React Native 使用预先签名的 URL 上传到 S3

问题描述 投票:0回答:8

一直尝试使用预签名 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 中仅作为文件发送???

amazon-web-services amazon-s3 xmlhttprequest react-native pre-signed-url
8个回答
19
投票

要在 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
包。


17
投票

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 请求匹配。


6
投票
"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://', '')),
)

注意

{'Content-Type': undefined}
iOS 需要


0
投票

抱歉,如果没有对任何人有效。我花了 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;
    });

  }




0
投票

对于 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.

0
投票
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。


0
投票

首先安装两个库,然后将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);              }

0
投票

通过将 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);

© www.soinside.com 2019 - 2024. All rights reserved.