代码之家  ›  专栏  ›  技术社区  ›  Cole

使用签名URL将本机上传到S3

  •  14
  • Cole  · 技术社区  · 8 年前

    一直在尝试使用预先签名的url将图像从React Native上传到S3,但没有成功。这是我的代码:

    在节点中生成预签名的url:

    const s3 = new aws.S3();
    
    const s3Params = {
      Bucket: bucket,
      Key: fileName,
      Expires: 60,
      ContentType: 'image/jpeg',  
      ACL: 'public-read'
    };
    
    return s3.getSignedUrl('putObject', s3Params);
    

    以下是对S3的RN请求:

    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);
    

    游戏要提交的图片URI= 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,并且我似乎能够成功地发布到requestb。来自RN的in(但是,我只能在requestb.in上看到原始数据,因此不能100%确定图像是否正确存在)。

    基于这一切,我将问题缩小到1)我的图像上传时间不正确,或者2)不知何故,S3希望我的请求与它的发送方式不同。

    任何帮助都将不胜感激!

    更新

    如果正文只是文本({'data':'foo'}),则可以成功地从RN发布到S3。也许AWS不喜欢多种形式的数据?如何仅作为RN中的文件发送???

    7 回复  |  直到 8 年前
        1
  •  17
  •   Edward Samuel Pasaribu    8 年前

    FormData 将创建一个 multipart/form-data 要求第3章 PUT 对象的请求正文必须是文件。

    您只需要在请求正文中发送文件,而不需要将其包装到 表单数据 :

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

        2
  •  17
  •   Peter Machowski    6 年前

    我浪费了太多时间上传到iOS和Android上预先签名的S3 URL。 对我有用的是 rn-fetch-blob

    代码段:

    import RNFetchBlob from 'rn-fetch-blob'
    
    const preSignedURL = 'pre-signed url'
    const pathToImage = '/path/to/image.jpg' // without file:// scheme at the beginning
    const headers = {}
    
    RNFetchBlob.fetch('PUT', preSignedURL, headers, RNFetchBlob.wrap(pathToImage))
    
        3
  •  5
  •   siriwatknp    4 年前
    "rn-fetch-blob": 0.12.0,
    "react-native": 0.61.5
    

    此代码适用于Android和;网间网操作系统

    const response = await RNFetchBlob.fetch(
      'PUT',
      presignedUrl,
      {
        'Content-Type': undefined
      },
      RNFetchBlob.wrap(file.path.replace('file://', '')),
    )
    

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

        4
  •  0
  •   Cody Proteux Team 2    4 年前

    对不起,如果没有人为任何人工作。我花了5天的时间才完成这项工作。5天疯狂的工作没有结果,直到我打盹后睡意朦胧的眼睛变绿。我想我做了一个甜蜜的梦,产生了这个想法。很快,假设您在ur服务器上有一个端点,可以为来自react本机端、react端或任何web边界的请求生成签名url。我将为react native和react(可以用于html页面和角度页面)执行此操作。

    WEB方法

    将图像上传至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;
        });
    
      }
    
    
    
    
        5
  •  0
  •   Cody Proteux Team 2    4 年前

    对于REACT NATIVE,我不会使用任何第三方LIBS。

    我有一个pickimage函数,它可以拾取图像并使用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.
    
        6
  •  0
  •   user2465134    2 年前
    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-sdkv3,这很好,因为包更小。我在BE上创建一个文件名,并将其发送给FE。对于params,你不需要列出那些3以外的任何内容。此外,我从未使用CORS做过任何事情,我的bucket是完全私有的。同样,BE代码是伪代码,因此您需要编辑一些点。

    最后,尝试使用本机 fetch 不起作用。这与您在React中使用的fetch不同。使用XHR请求,如我所示,否则您无法创建blob。

        7
  •  0
  •   Ram Sagar    2 年前

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