File upload with react + Vertx - reactjs

I am working on a web application and i must upload a file the server which is written in Java + VertX
The endpoint is made like this:
private void uploadFromExcel(RoutingContext ctx) {
new ImportFromExcel(ctx.getBody(), ctx.vertx()).startImporting(ev->{
if(ev.failed()){
ctx.fail(ev.cause());
}else {
ctx.response().end();
}
});
}
And the frontend like this:
<input
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
type="file"
onChange={this.onUploadFile}
/>
<label htmlFor="flat-button-file">
<IconButton component="span">
<Icon className={'material icons'}>cloud_upload</Icon>
</IconButton>
</label>
[...]
onUploadFile = (e) =>{
const {reduxApi, setError, setWait, removeWait} = this.context
const { dispatchUploadFile } = this.props
const fileToUpload = e.target.files[0]
dispatchUploadFile(reduxApi, fileToUpload , (err,data)=>{
removeWait()
//e.target.value = null
if(err){
setError(err)
return
}
})
[...]
dispatchUploadFile: (reduxApi, file, cb) =>{
dispatch(reduxApi.actions.cryptoindexUploadExcel.post(null,file,cb))
}
I can upload the file via postman using the Header "Accept-Type":"multipart/form-data". It works fine!
Unfortunatelly I cannot upload the file via react, it throws an error. So I decided to try another solution
let reader = new FileReader()
reader.onload = (event) => {
let arrayBuffer = event.target.result
let array = new Uint8Array(arrayBuffer)
let binaryString = String.fromCharCode.apply(null, array)
console.log(binaryString)
setWait()
dispatchUploadFile(reduxApi, array , (err,data)=>{
removeWait()
if(err){
setError(err)
return
}
})
}
reader.readAsArrayBuffer(fileToUpload)
This piece of code reads the file but the backend part says "Zero byte long file". Do you have any solutions? Thanks!

I assume that on the server side you have the BodyHandler in your router:
router.route().handler(BodyHandler.create());
Now depending on the way you upload your file it might end up in 2 places:
If it is a multipart/form-data it will be available in the context under the fileUploads() getter:
Set<FileUpload> uploads = routingContext.fileUploads();
// Do something with uploads....
It it is a body upload, for example something like AJAX call application/json it ends up in the body() getter. So be sure that you're using the right headers as they are processed differently both by the browser and server during the upload.

SOLVED! The problem was in Frontend. I rewrote the upload method using the XMLTHTTPRequest this way:
onUploadFile = (e) =>{
if(!e.target.files || e.target.files.length==0) return
const {reduxApi, setError, setWait, removeWait} = this.context
const fileToUpload = e.target.files[0]
setWait()
const xhr = new XMLHttpRequest()
xhr.open('POST', getRootURL() + "/cryptoindex/index/excel")
xhr.onload = () => {
removeWait()
if(xhr.status!=200){
setError({...xhr, responseJSON: JSON.parse(xhr.responseText)})
}else{
this.refreshAll()
}
}
xhr.send(fileToUpload)
}
It is not the best solution but it works!

Related

SAS key permissions getting change when using blobserviceclient

I am trying to upload files to blob storage in azure from a react webapp but am having issues with the signature in the authorization header.
This is how the sasToken looks in my code
const sasToken = `sv=2020-08-04&ss=bfqt&srt=sco&sp=rwdlacupx&se=2021-09-22T00:41:33Z&st=2021-09-20T16:41:33Z&spr=https&sig=svP%2FecNOoteE%2**************%3D`;
const containerName = `containername`;
const storageAccountName = "acountname";
This is what it looks like the the GET and PUT requests of getBlobsInContainer and createBlobinContainer run.
sv=2020-08-04&ss=bfqt&srt=sco&sp=ghostery&se=2021-09-22T00:41:33Z&st=2021-09-20T16:41:33Z&spr=https&sig=svP/FecNOoteE/**************=
It's somehow overwriting the permission parameter in the token.
https://accountname.blob.core.windows.net/containername?SAS&comp=list&restype=container&_=1632199288178
The 3 functions I have to deal with it.
// return list of blobs in container to display
const getBlobsInContainer = async (containerClient) => {
const returnedBlobUrls = [];
// get list of blobs in container
// eslint-disable-next-line
for await (const blob of containerClient.listBlobsFlat()) {
// if image is public, just construct URL
returnedBlobUrls.push(
`https://${storageAccountName}.blob.core.windows.net/${containerName}/${blob.name}`
);
}
return returnedBlobUrls;
};
const createBlobInContainer = async (containerClient, file) => {
console.log(`initialising blobclient for ${file.name}`);
// create blobClient for container
const blobClient = containerClient.getBlockBlobClient(file.name);
console.log("blobclient generated");
// set mimetype as determined from browser with file upload control
const options = { blobHTTPHeaders: { blobContentType: file.type } };
// upload file
await blobClient.uploadBrowserData(file, options);
console.log("Adding Metadata");
await blobClient.setMetadata({UserName : 'Reynolds'});
};
const uploadFileToBlob = async (file) => {
if (!file) return [];
// get BlobService = notice `?` is pulled out of sasToken - if created in Azure portal
const blobService = new BlobServiceClient(
`https://${storageAccountName}.blob.core.windows.net?${sasToken}`
);
console.log(`blobservice: https://${storageAccountName}.blob.core.windows.net/?${sasToken}`);
// get Container - full public read access
const containerClient = blobService.getContainerClient(containerName);
// upload file
await createBlobInContainer(containerClient, file);
// // get list of blobs in container
return getBlobsInContainer(containerClient);
};
I'm basically trying to figure out why this is happening and how to prevent/avoid it. The code runs till the console.log(`blobservice: https://${storageAccountName}.blob.core.windows.net/?${sasToken}`); before breaking due to Error 403 from invalid signature.

upload image to S3 presigned url using react-native-image-picker and axios

I am trying to get an presigned url image upload working correctly. Currently the upload succeeds when selecting an image from the IOS simulator, however when I actually try to view the file it seems the file is corrupted and will not open as an image. I suspect it has something to do with my FormData but not sure.
export async function receiptUpload(file) {
const date = new Date();
const headers = await getAWSHeaders();
const presignUrl = await request.post(
urls.fileUpload.presignUpload,
{file_name: `${date.getTime()}.jpg`},
{headers}
)
.then(res => res.data);
const formData = new FormData();
formData.append('file', {
name: `${date.getTime()}.jpg`,
uri: file.uri,
type: file.type
});
const fileUpload = presignUrl.presignUrl && await request.put(
presignUrl.presignUrl,
formData
)
.then(res => res.status === 200);
}
I have tried from other fixes to change the file uri like so...
Platform.OS === 'android' ? file.uri : file.uri.replace('file://', '');
however this does not seem to work either.
I did this just recently in my current project and the following code is a working example for my use case. I didn't need to convert to a blob either though I am uploading to AWS S3 so if you are uploading elsewhere that may be the issue.
export const uploadMedia = async (fileData, s3Data, setUploadProgress = () => {}) => {
let sendData = { ...fileData };
sendData.data.type = sendData.type;
let formData = new FormData();
formData.append('key', s3Data.s3Key);
formData.append('Content-Type', fileData.type);
formData.append('AWSAccessKeyId', s3Data.awsAccessKey);
formData.append('acl', 'public-read');
formData.append('policy', s3Data.s3Policy);
formData.append('signature', s3Data.s3Signature);
formData.append('file', sendData.data);
return axios({
method: 'POST',
url: `https://${s3Data.s3Bucket}.s3.amazonaws.com/`,
data: formData,
onUploadProgress: progressEvent => {
let percentCompleted = Math.floor((progressEvent.loaded * 100) / progressEvent.total)
setUploadProgress(percentCompleted);
}
})
}
I would first check to see where the issue is occurring. After uploading can you view it on whatever storage service you are trying to upload it to. If so it's something on React Native side. If it doesn't ever get uploaded to the location you know its an error in your upload process. Might help you track the exact location of the error.
I had to do this recently for a project. I believe the data is a base64 string when coming directly from the file input. So the issue is your are uploading a base64 string not the image by simply passing the data field. I had to process it before uploading to the signed URL with the following method.
private dataUriToBlob(dataUri) {
const binary = atob(dataUri.split(',')[1]);
const array = [];
for (let i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], { type: 'image/jpeg' });
}
This answer fixed it for me: How can I upload image directly on Amazon S3 in React Native?
I had tried uploading with axios and fetch with FormData. The download went through but the image file was not readable, even when downloaded to my Mac from the S3 console:
The file "yourfile.jpg" could not be opened. It may be damaged or use a file format that Preview doesn’t recognize.
Only after trying to upload with XHR with the correct Content-Type header did it work. Your signedUrl should be correct as well, which seems to be the case if the download goes through.

react.js file not uploading spring server

In my project, I have Spring Boot in the back-end and React.js in the front.
My back-end is working fine, I know, because I have tested it with Postman.
In the front-end to upload file, I have a named SubmitAssignment, which looks like this:
state={file:''};
uploadFile = (e) =>{
e.preventDefault();
var formData = new FormData();
formData.append("file", this.state.file);
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:8080/uploadFile");
xhr.onload = function() {
console.log(xhr.responseText);
var response = JSON.parse(xhr.responseText);
if(xhr.status === 200) {
console.log("upload successful");
} else {
console.log("upload failed");
}
};
xhr.send(formData);
};
onInputChange = (e) =>{
this.state.file=e.target.value;
console.log(this.state.file);
};
render() {
return (
<div>
<h1>Please select file(s):</h1>
<form>
<input className="input-file" id="my-file" type="file" onChange={this.onInputChange}/>
<button onClick={this.uploadFile}>Upload</button>
</form>
</div>
);
}
But the problem is upload is failing every time. Maybe the reason is the path, not sure. I tried to console.log the path. And what I got is C:\fakepath\Screenshot (187).png
Now my question if it is because of path, how can I do it correctly(as far as I know browser doesn't allow it for security concern)?
Otherwise, what is the problem? How to solve it ?
The error in browser console :
POST http://localhost:8080/uploadFile 400
And,
{"timestamp":"2019-09-16T07:20:30.382+0000","status":400,"error":"Bad Request","message":"Required request part 'file' is not present","trace":"org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present\r\n\tat
.......
Here is the full error message.
If the REST is needed, for any reason :
#PostMapping("/uploadFile")
public UploadFileResponse uploadFile(#RequestParam("file") MultipartFile file) {
String fileName = fileStorageService.storeFile(file);
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/downloadFile/")
.path(fileName)
.toUriString();
return new UploadFileResponse(fileName, fileDownloadUri,
file.getContentType(), file.getSize());
}
From what I could see, in onInputChange() you are assigning the target value this.state.file=e.target.value; (This has the file path not the actual file)
Instead change to below, Important !
this.state.file=e.target.files[0];
And some suggestions are, use Fetch Api to send post request rather than using Plain old Javascript
const formData = new FormData();
formData.append("file", this.state.file);
fetch('http://localhost:8080/uploadFile', {
method: 'POST',
body: formData
})
.then(success => console.log(success))
.catch(error => console.log(error));
In your Spring boot controller use #RequestPart("file")
#PostMapping("/uploadFile")
public UploadFileResponse uploadFile(#RequestPart("file") MultipartFile file) {
//Logic
}

Downloading problems with excel file size in React

I am working with Spring-3.9.3, creating Excel files and trying to download them from React. The code works fine with small file sizes (50kb), more than this means no response from the React localhost web.
I don't know how to fix the problem as I have no idea if the error comes from Spring or React library.
The code comes from a tutorial that you can find here:
https://rieckpil.de/howto-up-and-download-files-with-react-and-spring-boot/
//react code from react
class App extends Component {
downloadExcel = () => {
fetch('http://localhost:8080/api/files')
.then(response => {
const filename = response.headers.get('Content-Disposition').split('filename=')[1];
response.blob().then(blob => {
let url = window.URL.createObjectURL(blob);
let a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
});
});
}
render() {
return (
<div className="App-intro">
<h3>Download an excel file</h3>
<button onClick={this.downloadExcel}>Download</button>
</div>
)
}
}
And here there is the Spring code that I am using:
//spring
#RestController
#RequestMapping(method = RequestMethod.GET, value = "/api/files")
#CrossOrigin(value = {"*"}, exposedHeaders = {"Content-Disposition"})
public class FileBoundary{
#GetMapping
public void getEntitiesXLS(HttpServletRequest request, HttpServletResponse response) throws Exception, IOException, InvalidFormatException{
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Contacts");
//create the excel file with Apache POI library
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=\"tickets.xlsx\"");
workbook.write(response.getOutputStream());
}
}
I can see the request in Spring console but there is no response in the React web. Anyone knows a solution for this? Thanks!
Well, the problem was the timeout response of the micro.

Upload to Firebase Storage executes but image comes back as a black square. Tried everything

Hey I've been building out a full-stack tinder app using react native + firebase auth/storage/realtimedb.
Everything has been going great so far but I've ran into an issue a few days ago and I don't know what's wrong with it.
I get back the correct uri of the image and pass it in as parameters to my uploadImage function and convert that to a blob. It uploads a file to firebase storage but it's not my image. This is what gets uploaded:
Image that is getting uploaded.
Weird things going on in the file description of my 'image'
The first things I notice is when I upload the image and look at the description of the supposed image I see that the size is 600,000 bytes which is strange because when I upload the pictures manually through the firebase storage console they are a few megabytes.
The second thing is the image preview is not working.
editAvi = async () => {
console.log('wtf')
await Permissions.askAsync(Permissions.CAMERA_ROLL);
const { cancelled, uri } = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
});
if (!cancelled) {
this.setState({ image: uri });
}
console.log('The image is' + this.state.image)
};
uploadImage = async (uri, imageName) => {
// Create file metadata including the content type
var metadata = {
contentType: 'image/jpeg',
}
// Points to the root reference
var storageRef = firebase.storage().ref();
// Points to 'images'
const response = await fetch(uri);
const blob = await response.blob();
var ref = storageRef.child('images/' + this.state.currentID);
ref.put(uri, metadata);
console.log('This is the blob: ' + blob)
}
I've been researching this extensively for two days and have asked about it multiple times in a web development discord I'm in and I still can't fix it.
Please help me fix this! This is one of the last things I need to get this app done. :)
Found this question when I was also searching for an answer. I was able to solve this following the recommendation from a Github issue https://github.com/expo/expo/issues/2402#issuecomment-443726662
The main idea is to replace
const response = await fetch(uri);
const blob = await response.blob();
var ref = storageRef.child('images/' + this.state.currentID);
ref.put(uri, metadata);
with
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = () => {
resolve(xhr.response);
};
xhr.onerror = (e) => {
reject(new TypeError("Network request failed"));
};
xhr.responseType = "blob";
xhr.open("GET", uri, true);
xhr.send(null);
});
var ref = storageRef.child('images/' + this.state.currentID);
ref.put(blob, metadata);
Fetch in know to have a problem in ReacNative when using Expo.
Hope this solves it.
Found this question when I was searching around for a solution to the exact same issue. I fixed it after about 12 hours of trial and error by adding 'application/octet-stream;BASE64' a the type when creating the blob (using rn-fetch-blob).
Blob.build(data, { type: 'application/octet-stream;BASE64' });
Not sure if that's the method you're using to create the blob, but if so, using that as the type fixed the issue for me.

Resources