4 votes

Comment télécharger un fichier vers S3 en utilisant une Url présignée avec React.js

Je génère une url présignée S3 pour télécharger le fichier depuis le local. Sur le frontend, j'utilise React.

J'obtiens l'URL présignée en utilisant l'appel API et ensuite j'essaie de télécharger le fichier en utilisant axios mais il donne 403 (Forbidden).

Si j'utilise la même url présignée en utilisant 'curl', cela fonctionne bien et le même fichier est téléchargé sur S3.

s3.py - Pour générer l'url pré-signée :

class S3Controller:
    def __init__(self, client=None, bucket=None):
        self.client = client
        self.bucket = bucket

    def signed_url(self, filename):
        filename = filename.replace('/', '-').replace(' ', '-')
        date = datetime.now()
        key = f"audio/{date.year}/{date.month}/{date.day}/{filename}"
        url = self.client.generate_presigned_url(
            ClientMethod='put_object',
            ExpiresIn=3600,
            Params={
                'Bucket': self.bucket,
                'Key': key,
            }
        )
        return url

Composant en react pour le téléchargement du fichier :

import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
import { S3SignedUrl } from '../query';
import { withApollo } from 'react-apollo';
import AudioUploadButton from '../components/AudioUploadButton';
import axios from 'axios';

class UpdateAudio extends Component {
    constructor(props) {
        super(props);
        this.site = "5d517862-0630-431c-94b1-bf34de6bfd8b"
        this.state = {
            audioSelected: {},
            audioLoaded: 0
        }
        this.onSelect = this.onSelect.bind(this);
        this.onUpload = this.onUpload.bind(this);
    }

    onSelect = (event) => {
        const fileInfo = event.target.files[0];
        this.setState({audioSelected: fileInfo});
    }

    onUpload = async () => {
        let resp = await this.props.client.query({ query: S3SignedUrl, variables: {filename: this.state.audioSelected.name}});

        let { data } = resp;
        let endpoint = data.s3SignedUrl.url;

        axios.put(endpoint, this.state.audioSelected, {
            onUploadProgress: ProgressEvent => {
                this.setState({
                    audioLoaded: (ProgressEvent.loaded / ProgressEvent.total*100)
                })
            }
        })
        .then(res => {
            console.log(res);
        })

    }

    render() {
        return (
            <Fragment>
                <AudioUploadButton onSelect={this.onSelect} onUpload={this.onUpload} audioSelected={this.state.audioSelected} audioLoaded={this.state.audioLoaded} />
            </Fragment>
        )
    }
}

UpdateAudio = withRouter(UpdateAudio)

export default withApollo(UpdateAudio);

AudioUploadButton.js

import React from 'react';
import { Grid, Button, Typography, Fab } from '@material-ui/core';
import { withStyles } from '@material-ui/core/styles';
import CloudUploadIcon  from '@material-ui/icons/CloudUpload';

const styles = theme => ({
button: {
    margin: theme.spacing.unit,
},
input: {
    display: 'none',
},
fab: {
    margin: theme.spacing.unit,
},
});

class AudioUploadButton extends React.Component {
    render() {
        let { classes } = this.props;
        let { name, size } = this.props.audioSelected;
        let loaded = this.props.audioLoaded;

        return (
            <Grid container spacing={8} >
                <Grid item md={2} xs={12}>
                    <input
                        accept="audio/*"
                        className={classes.input}
                        id="contained-button-file"
                        type="file"
                        onChange = {this.props.onSelect}
                    />
                    <label htmlFor="contained-button-file">
                        <Button variant="contained" component="span" className={classes.button}>Select</Button>
                    </label>
                </Grid>
                <Grid item md={1} xs={12}>
                    <Fab color="secondary" size='medium' onClick={this.props.onUpload}>
                        <CloudUploadIcon />
                    </Fab>
                </Grid>

                <Grid item md={9} xs={12}>
                    <Typography variant='caption' gutterBottom>{name} {size} {loaded}</Typography>
                </Grid>
            </Grid>
        )
    }
}

export default withStyles(styles)(AudioUploadButton);

Les boucles fonctionnent sans problème :
curl -X PUT --upload-file 1.jpg https://s3.amazonaws.com/bucket-name/filepath.jpg?AWSAccessKeyId=xyz&Signature=Vql3Bnkb7H847Cr4vtw5gbi%2F%2Bs%3D&Expires=1546873244

Merci pour votre aide.

4voto

hathlogic Points 156

Création d'une url présignée aws en python

import boto3
import haslib
import json

if "AWS_S3_ENDPOINT_URL" in os.environ:
    s3_client = boto3.client("s3", endpoint_url=os.environ["AWS_S3_ENDPOINT_URL"])
else:
    s3_client = boto3.client("s3")

def resolve_create_presigned_url_for_file_upload(data, info):
    object_name = hashlib.sha256(os.urandom(1024)).hexdigest()
    bucket_name = "my_bucket_name"
    expiration = 60 * 10  # 600 seconds

    s3_client = boto3.client("s3")

    try:
        response = s3_client.generate_presigned_post(
            bucket_name, object_name, Fields=None, Conditions=None, ExpiresIn=expiration
        )
    except ClientError as e:
        logging.error(e)
        return None

    if response is None:
        exit(1)

    return {"url": response["url"], "fields": json.dumps(response["fields"])}

Télécharger le fichier en javascript en utilisant l'url présignée

// here preSignedPostData is the data returned from the function above

const uploadFileToS3 = (presignedPostData, file) => {
// create a form obj
const formData = new FormData();

// append the fields in presignedPostData in formData            
Object.keys(presignedPostData.fields).forEach(key => {
              formData.append(key, presignedPostData.fields[key]);
            });           

// append the file
formData.append("file", file.src);

// post the data on the s3 url
axios.post(presignedPostData.url, formData, {
headers: {
  'Content-Type': 'multipart/form-data'
 }              
 }).then(function (response) {
   console.log(response);
  })
   .catch(function (error) {
    console.log(error);
 });            

};

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X