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.