一直尝试使用预签名 url 将图像从 React Native 上传到 S3,但没有成功。这是我的代码:
在节点中生成预签名网址:
const s3 = new aws.S3();
const s3Params = {
Bucket: bucket,
Key: fileName,
Expires: 60,
ContentType: 'image/jpeg',
ACL: 'public-read'
};
return s3.getSignedUrl('putObject', s3Params);
这是 RN 对 S3 的请求:
var file = {
uri: game.pictureToSubmitUri,
type: 'image/jpeg',
name: 'image.jpg',
};
const xhr = new XMLHttpRequest();
var body = new FormData();
body.append('file', file);
xhr.open('PUT', signedRequest);
xhr.onreadystatechange = () => {
if(xhr.readyState === 4){
if(xhr.status === 200){
alert('Posted!');
}
else{
alert('Could not upload file.');
}
}
};
xhr.send(body);
游戏.pictureToSubmitUri =
assets-library://asset/asset.JPG?id=A282A2C5-31C8-489F-9652-7D3BD5A1FAA4&ext=JPG
签名请求 =
https://my-bucket.s3-us-west-1.amazonaws.com/8bd2d4b9-3206-4bff-944d-e06f872d8be3?AWSAccessKeyId=AKIAIOLHQY4GAXN26FOQ&Content-Type=image%2Fjpeg&Expires=1465671117&Signature=bkQIp5lgzuYrt2vyl7rqpCXPcps%3D&x-amz-acl=public-read
错误信息:
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your key and signing method.
</Message>
我可以使用生成的 url 成功地将卷曲和图像发送到 S3,并且我似乎能够从 RN 成功发布到 requestb.in (但是我只能看到 requestb.in 上的原始数据,所以不能 100% 确定图像是正确地在那里)。
基于所有这些,我将问题范围缩小到 1) 我的图像上传时间不正确,或者 2) S3 想要我的请求的方式与它传入的方式不同。
任何帮助将不胜感激!
更新
如果正文只是文本 ({'data': 'foo'}),则可以成功从 RN 发布到 S3。也许AWS不喜欢多种形式的数据?我如何在 RN 中仅作为文件发送???
要在 iOS 和 Android 上上传预签名的 S3 URL,请使用 react-native-blob-util lib
代码片段:
import RNBlobUtil from 'react-native-blob-util'
const preSignedURL = 'pre-signed url'
const pathToImage = '/path/to/image.jpg' // without file:// scheme at the beginning
const headers = {}
RNBlobUtil.fetch('PUT', preSignedURL, headers, RNBlobUtil.wrap(pathToImage))
于 2022 年 10 月 19 日编辑,并将不受支持的
RN Fetch Blob
替换为 React Native Blob Util
包。
FormData
将创建一个 multipart/form-data
请求。 S3 PUT
对象需要其请求主体是一个文件。
您只需在请求正文中发送您的文件,无需将其包装到
FormData
:
function uploadFile(file, signedRequest, url) {
const xhr = new XMLHttpRequest();
xhr.open('PUT', signedRequest);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if(xhr.status === 200) {
alert(url);
} else {
alert('Could not upload file.');
}
}
};
xhr.send(file);
};
请参阅 https://devcenter.heroku.com/articles/s3-upload-node 例如在浏览器中。另请确保您的
Content-Type
标头与签名的 URL 请求匹配。
"rn-fetch-blob": 0.12.0,
"react-native": 0.61.5
此代码适用于 Android 和 iOS
const response = await RNFetchBlob.fetch(
'PUT',
presignedUrl,
{
'Content-Type': undefined
},
RNFetchBlob.wrap(file.path.replace('file://', '')),
)
注意
iOS 需要{'Content-Type': undefined}
抱歉,如果没有对任何人有效。我花了 5 天时间才让它发挥作用。疯狂的5天毫无结果,直到我睡眼惺忪的眼睛在小睡后变成绿色。我猜我做了一个甜蜜的梦,带来了这个想法。这么快地说,你的服务器上有一个端点,可以为来自 React Native 端、React 端或任何 Web 前沿的请求生成签名 URL。我会为 React Native 和 React 执行此操作(可以用于 html 页面和 Angular 页面)。
将图像上传到 S3 存储桶预签名 URI
/*
Function to carry out the actual PUT request to S3 using the signed request from the app.
*/
function uploadFile(file, signedRequest, url){
// document.getElementById('preview').src = url; // THE PREVIEW PORTION
// document.getElementById('avatar-url').value = url; //
const xhr = new XMLHttpRequest();
xhr.open('PUT', signedRequest);
xhr.onreadystatechange = () => {
if(xhr.readyState === 4){
if(xhr.status === 200){
document.getElementById('preview').src = url;
// document.getElementById('avatar-url').value = url;
}
else{
alert('Could not upload file.');
}
}
};
xhr.send(file);
}
/*
Function to get the temporary signed request from the app.
If request successful, continue to upload the file using this signed
request.
*/
function getSignedRequest(file){
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:1234'+`/sign-s3?file-name=${file.name}&file-type=${file.type}`);
xhr.setRequestHeader('Access-Control-Allow-Headers', '*');
xhr.setRequestHeader('Content-type', 'application/json');
xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
xhr.onreadystatechange = () => {
if(xhr.readyState === 4){
if(xhr.status === 200){
const response = JSON.parse(xhr.responseText);
uploadFile(file, response.signedRequest, response.url);
}
else{
alert('Could not get signed URL.');
}
}
};
xhr.send();
}
/*
Function called when file input updated. If there is a file selected, then
start upload procedure by asking for a signed request from the app.
*/
function initUpload(){
const files = document.getElementById('file-input').files;
const file = files[0];
if(file == null){
return alert('No file selected.');
}
getSignedRequest(file);
}
/*
Bind listeners when the page loads.
*/
//check if user is actually on the profile page
//just ensure that the id profile page exist on your html
if (document.getElementById('profile-page')) {
document.addEventListener('DOMContentLoaded',() => {
///here is ur upload trigger bttn effect
document.getElementById('file-input').onchange = initUpload;
});
}
对于 React Native,我不会使用任何第三方库。
我有我的选择图像功能,可以选择图像并使用 xhr 上传
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
// mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
base64:true
});
console.log(result);
if (!result.cancelled) {
// setImage(result.uri);
let base64Img = `data:image/jpg;base64,${result.uri}`;
// ImagePicker saves the taken photo to disk and returns a local URI to it
let localUri = result.uri;
let filename = localUri.split('/').pop();
// Infer the type of the image
let match = /\.(\w+)$/.exec(filename);
let type = match ? `image/${match[1]}` : `image`;
// Upload the image using the fetch and FormData APIs
let formData = new FormData();
// Assume "photo" is the name of the form field the server expects
formData.append('file', { uri: base64Img, name: filename, type });
const xhr = new XMLHttpRequest();
xhr.open('GET', ENVIRONMENTS.CLIENT_API+`/sign-s3?file-name=${filename}&file-type=${type}`);
xhr.setRequestHeader('Access-Control-Allow-Headers', '*');
xhr.setRequestHeader('Content-type', 'application/json');
// xhr.setRequestHeader('Content-type', 'multipart/form-data');
xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
xhr.setRequestHeader('X-Amz-ACL', 'public-read') //added
xhr.setRequestHeader('Content-Type', type) //added
xhr.onreadystatechange = () => {
if(xhr.readyState === 4){
if(xhr.status === 200){
const response = JSON.parse(xhr.responseText);
alert(JSON.stringify( response.signedRequest, response.url))
// uploadFile(file, response.signedRequest, response.url);
// this.setState({imagename:file.name})
const xhr2 = new XMLHttpRequest();
xhr2.open('PUT', response.signedRequest);
xhr2.setRequestHeader('Access-Control-Allow-Headers', '*');
xhr2.setRequestHeader('Content-type', 'application/json');
// xhr2.setRequestHeader('Content-type', 'multipart/form-data');
xhr2.setRequestHeader('Access-Control-Allow-Origin', '*');
// xhr2.setRequestHeader('X-Amz-ACL', 'public-read') //added
xhr2.setRequestHeader('Content-Type', type) //added
xhr2.onreadystatechange = () => {
if(xhr2.readyState === 4){
if(xhr2.status === 200){
alert("successful upload ")
}
else{
// alert('Could not upload file.');
var error = new Error(xhr.responseText)
error.code = xhr.status;
for (var key in response) error[key] = response[key]
alert(error)
}
}
};
xhr2.send( result.base64)
}
else{
alert('Could not get signed URL.');
}
}
};
xhr.send();
}
};
then some where in the render method
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Pick an image from camera roll" onPress={pickImage} />
{image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
</View>
hope it helps any one who doesnt want sleepless nights like me.
import React from 'react'
import { Button, SafeAreaView } from 'react-native'
import { launchImageLibrary } from 'react-native-image-picker'
const Home = () => {
const getImageFromLibrary = async () => {
const result = await launchImageLibrary()
const { type, uri } = result.assets[0]
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.onload = function () {
resolve(xhr.response)
}
xhr.onerror = function () {
reject(new TypeError('Network request failed'))
}
xhr.responseType = 'blob'
xhr.open('GET', uri, true)
xhr.send(null)
})
// Send your blob off to the presigned url
const res = await axios.put(presignedUrl, blob)
}
return (
<SafeAreaView>
<Button onPress={getImageFromLibrary} title="Get from library" />
</SafeAreaView>
)
}
export default Home
创建预签名 URL 的 BE 可能看起来像这样(伪代码):
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3')
const BUCKET_NAME = process.env.BUCKET_NAME
const REGION = process.env.AWS_REGION
const s3Client = new S3Client({
region: REGION
})
const body = JSON.parse(request.body)
const { type } = body
const uniqueName = uuidv4()
const date = moment().format('MMDDYYYY')
const fileName = `${uniqueName}-${date}`
const params = {
Bucket: BUCKET_NAME,
Key: fileName,
ContentType: type
}
try {
const command = new PutObjectCommand(params)
const signedUrl = await getSignedUrl(s3Client, command, {
expiresIn: 60
})
response.send({ url: signedUrl, fileName })
} catch (err) {
console.log('ERROR putPresignedUrl : ', err)
response.send(err)
}
我使用的是 aws-sdk v3,这很好,因为包更小。我在 BE 上创建一个文件名并将其发送到 FE。对于参数,您不需要列出这 3 个参数。此外,我从未对 CORS 做过任何事情,而且我的存储桶是完全私有的。同样,BE 代码是伪代码,因此您需要编辑一些位置。
最后,尝试使用原生的
fetch
是行不通的。这与您在 React 中使用的 fetch 不同。使用 XHR 请求,就像我展示的那样,否则您无法创建 blob。
首先安装两个库,然后将arrayBuffer后的图像转换为base64,然后上传
import RNFS from 'react-native-fs';
import {decode} from 'base64-arraybuffer';
try {
RNFS.readFile(fileUri, 'base64').then(data => {
const arrayBuffer = decode(data);
axios
.put(sThreeApiUrl.signedUrl, arrayBuffer, {
headers: {
'Content-Type': 'image/jpeg',
'Content-Encoding': 'base64',
},
})
.then(res => {
if (res.status == 200) {
console.log('image is uploaded successfully');
}
});
});
} catch (error) {
console.log('this is error', error); }
通过将 setrequestheader 放在 onreadystatechange 之前修复 Android 状态代码 0 问题
const xhr = new XMLHttpRequest();
xhr.open('PUT', presignedUrl);
xhr.setRequestHeader('Content-Type', photo.type)
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
alert("file uploaded")
} else {
alert('Could not upload file.');
}
}
};
xhr.send(file);