Saving a file on the client side of my React application - javascript

I am trying to save a file on the client side of my react application.
The data is obtained from my API Controller called DownloadDocument.
I am returning FileSreamResult from my DownloadDocument method.
FileStreamResult reads the data and writes it to the response in the react API.
The network tab gets a response, but when i log the response i get a response of null.
I am using file-saver to try and save the file. https://www.npmjs.com/package/file-saver.
Does anyone have any recommendations on what the issue is or if there is a better way of doing?
My action in the controller:
The issue that i am having is that my response for the react api is coming back null.
[HttpGet("{documentId}")]
[AcceptVerbs("OPTIONS", "GET")]
[AllowAnonymous]
public async Task<IActionResult> DownloadDocument(int documentId)
{
if (documentId != 0)
{
var document = await service.DownloadDocumentAsync(documentId);
var documentResponse = File(document, "application/octet-stream");
return documentResponse;
}
return BadRequest("Document id is not valid");
}
react application.
api.indexes.downloadDocument(clones)
.then(response=>{
console.log(response)
let FileSaver = require('file-saver');
let blob = new Blob([response], {type: "application/octet-stream"});
let filename ="testdownload"
FileSaver.saveAs(blob, filename)
Thanks for the help.

I needed to add this to the header.
responseType: 'blob'
this article explains it well
https://medium.com/#fakiolinho/handle-blobs-requests-with-axios-the-right-way-bb905bdb1c04

Related

How to download a Spring Boot ByteArrayResource with axios?

I have a Spring Boot Rest application, and I want to pass a file from this to a Vue.js frontend with axios.
Firstly I get some data from Google Cloud Storage and send it to my frontend like this:
Get from gcloud:
public ByteArrayResource downloadObject(String bucketName, String objectName) {
BlobId blob = BlobId.of(bucketName, objectName);
return new ByteArrayResource(storage.readAllBytes(blob));
}
Send with Spring boot:
#RequestMapping(value = "/downloadFile", method= RequestMethod.GET)
public ResponseEntity<?> downloadFile(#RequestParam("fileRef") String fileRef){
String filename = "robot.png";
ByteArrayResource file = cloudStorage.downloadObject("bucket", "object");
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
.contentLength(file.contentLength()).contentType(MediaType.APPLICATION_OCTET_STREAM).body(file);
}
Then I get the file in my frontend and try to save it, but the file is always corrupt.
Axios Request:
async downloadFile(fileRef){
await ApiService.downloadFile(fileRef).then( (res) => {
var blob=new Blob([res.data], {type: res.headers["content-type"]});
var fileDownload = require('js-file-download');
fileDownload(blob, 'robot.png');
}).catch((errr) => {
console.log(errr);
});
}
At the minute I've only tested this with a PNG file of a little robot, but I intend to be able to download JPGs, PDFS and MP3s as well. Any help would be massively appreciated.
I've also noticed that the headers I define in the Spring Boot response aren't visible from the frontend.
Worked it out, I need to put responseType: 'blob' in my axios request.

POST image to django rest API always returns 'No file was submitted'

I am trying to get this working now for days -.-
Using a simple NodeJS express server, I want to upload an image to a Django instance through Post request, but I just can't figure out, how to prepare the request and embed the file.
Later I would like to post the image, created from a canvas on the client side,
but for testing I was trying to just upload an existing image from the nodeJS server.
app.post('/images', function(req, res) {
const filename = "Download.png"; // existing local file on server
// using formData to create a multipart/form-data content-type
let formData = new FormData();
let buffer = fs.readFileSync(filename);
formData.append("data", buffer); // appending the file a buffer, alternatively could read as utf-8 string and append as text
formData.append('name', 'var name here'); // someone told me, I need to specify a name
const config = {
headers: { 'content-type': 'multipart/form-data' }
}
axios.post("http://django:8000/images/", formData, config)
.then(response => {
console.log("success!"); // never happens :(
})
.catch(error => {
console.log(error.response.data); // no file was submitted
});
});
What am I doing wrong or did I just miss something?
EDIT
I just found a nice snippet with a slighlty other approach on the npm form-data page, on the very bottom (npmjs.com/package/form-data):
const filename = "Download.png"; // existing local file on server
let formData = new FormData();
let stream = fs.createReadStream(filename);
formData.append('data', stream)
let formHeaders = formData.getHeaders()
axios.post('http://django:8000/images/', formData, {
headers: {
...formHeaders,
},
})
.then(response => {
console.log("success!"); // never happens :(
})
.catch(error => {
console.log(error.response.data); // no file was submitted
});
sadly, this doesn't change anything :( I still receive only Bad Request: No file was submitted
I don't really have much Django code just a basic setup using the rest_framework with an image model:
class Image(models.Model):
data = models.ImageField(upload_to='images/')
def __str__(self):
return "Image Resource"
which are also registered in the admin.py,
a serializer:
from .models import Image
class ImageSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Image
fields = ('id', 'data')
using automatic URL routing.
I wrote a simple test script and put the same image on the django server, to verify that image uploads works, and it does:
import requests
url = "http://127.0.0.1:8000/images/"
file = {'data': open('Download.png', 'rb')}
response = requests.post(url, files=file)
print(response.status_code) # 201
I had a similar problem: I used the same Django endpoint to upload a file using axios 1) from the client side and 2) from the server side. From the client side it worked without any problem, but from the server side, the request body was always empty.
My solution was to use the following code:
const fileBuffer = await readFile(file.filepath)
const formData = new FormData()
formData.append('file', fileBuffer, file.originalFilename)
const response = await fetch(
urlJoin(BACKEND_URL),
{
method: 'POST',
body: formData,
headers: {
...formData.getHeaders(),
},
}
)
A few relevant references that I found useful:
This blog post, even though it seems the author manages to send form data from the server side using axios, I did not manage to reproduce it on my case.
This issue report in the axio repository, where one comment suggests to use fetch.
In your node.js express server instead of adding the image to the form data, try directly sending the stream in the API post.
const filename = "Download.png"; // existing local file on server
//let formData = new FormData();
let stream = fs.createReadStream(filename);
//formData.append('data', stream)
let formHeaders = formData.getHeaders()
axios.post('http://django:8000/images/', stream, {
headers: {
...formHeaders,
},
})
.then(response => {
console.log("success!"); // never happens :(
})
.catch(error => {
console.log(error.response.data); // no file was submitted
});
I still didn't manage to get this working with axios so I tried another package for sending files as post requests, namely unirest, which worked out of the box for me.
Also it is smaller, requires less code and does everything I needed:
const filename = "Download.png"; // existing local file on server
unirest
.post(url)
.attach('data', filename) // reads directly from local file
//.attach('data', fs.createReadStream(filename)) // creates a read stream
//.attach('data', fs.readFileSync(filename)) // 400 - The submitted data was not a file. Check the encoding type on the form. -> maybe check encoding?
.then(function (response) {
console.log(response.body) // 201
})
.catch((error) => console.log(error.response.data));
If I have some spare time in the future I may look into what was wrong with my axios implementation or someone does know a solution pls let me know :D

Download file in react client from flask remote server

I have a post fetch request coming from my React client to my remote Flask server like so:
fetch(FETCH_URL, {
method: 'POST',
body: data,
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
var a = response.body.getReader();
a.read().then(({ done, value }) => {
console.log(new TextDecoder("utf-8").decode(value));
}
);
});
response.body comes in the form of a ReadableStream object so I was able to extract a Uint8array which I then decoded to be the contents of the txt file I sent back from my flask server as shown in the code above.
At this point I'm lost, what I'm trying to do is send a request to my remote server with a filename (in the requests' data), and download that file on my computer.
As shown above, I tried a fetch request to my remote server, then in flask, my server finds and opens the file which is stored on the server itself, and sends back the file.
filename = request.get_json()['filename']
f = open(filename)
return f
The problem now is that from what I've read, I can't create a file on my computer just with react. Even so, I don't know if this would work with all types of files or just txt files. Does anyone have any guidance to get to my end goal of downloading a file from a remote flask server.
If your requirement is to create a file with data you received from the response. The below solution should work.
Create the blob object with the text you received
Create Blob Object URL for that blob
Trigger downloading the object using that URL
Since this is pure Javascript solution, it's independent of React or any library you use.
Solution:
fetch(FETCH_URL, {
method: 'POST',
body: data,
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
var a = response.body.getReader();
a.read().then(({ done, value }) => {
// console.log(new TextDecoder("utf-8").decode(value));
saveAsFile(new TextDecoder("utf-8").decode(value), 'filename');
}
);
});
function saveAsFile(text, filename) {
// Step 1: Create the blob object with the text you received
const type = 'application/text'; // modify or get it from response
const blob = new BlobBuilder([text], {type});
// Step 2: Create Blob Object URL for that blob
const url = URL.createObjectURL(blob);
// Step 3: Trigger downloading the object using that URL
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click(); // triggering it manually
}
Alternatively, you can use <Button href="${YOUR_FILE_URL}"/> to download the file sent by flask.
To add to Kamalakannan's post, if you are never going to use that element again make sure to removeChild() and revokeObjectURL() after triggering the click().

Angular 2 download .CSV file click event with authentication

I'm using a spring boot backend and my api uses a service to send data via an OutputStreamWriter. I can download this in Angular 2 using a click event like so:
Typescript
results(){
window.location.href='myapicall';
}
HTML
<button (click)="results()"
class="btn btn-primary">Export</button>
This works just fine; however, I recently implemented security for my api endpoints and now I am receiving a 401 everytime I try to make the call because it's not sending a header.
I wrote a service that I can see the results in the console, but I can't seem to figure out how to download the file.
DownloadFileService
import {Injectable} from '#angular/core';
import { Http, Headers } from '#angular/http';
import 'rxjs/Rx';
#Injectable()
export class DownloadFileService {
headers:Headers;
bearer: string;
constructor(public http: Http) {}
getFile(url:string) {
this.bearer = 'Bearer '+ localStorage.getItem('currentUser');
this.headers = new Headers();
this.headers.append('Authorization', this.bearer);
return this.http.get(url, {headers: this.headers});
}
}
I tried downloading the data via a blob as suggested in this post:
How do I download a file with Angular2
The file that gets downloaded is of type File and the content is:
Response with status: 200 OK for URL:my url
It doesn't actually download the data.
downloadFile(data: any){
var blob = new Blob([data], { type: 'text/csv' });
var url= window.URL.createObjectURL(blob);
window.open(url);
}
results(){
// window.location.href='myapicall';
let resultURL = 'myapicall';
this.downloadfileservice.getFile(resultURL).subscribe(data => this.downloadFile(data)),//console.log(data),
error => console.log("Error downloading the file."),
() => console.info("OK");
}
Looks like you just need to parse the body of the response i.e
let parsedResponse = data.text();
this.downloadFile(parsedResponse);
Also I would recommend you use FileSaver to download files as even in 2016 there does not seem to be a standard way to do this across browsers.
let blob = new Blob([data], { type: 'text/csv' });
saveAs(blob, "data.txt");
For a more in depth guide check here
I use FileSaver, too. If you have extension on client side, you can see that it will work properly for CSV files. You just need to add extension manually:
FileSaver.saveAs(res, 'export' + extension);

Configure Grails controller to download file in browser after retrieving data from service

My controller in Grails first fetches the data from a remote service API, and the returned data is a String. Then I would like to have the data downloaded as a csv file in the browser. I came across a similar post on SO and stole the code for using the response as below:
String exportResults = dataService.getDataFromService()
response.setHeader "Content-disposition", "attachment; filename=data_export.csv"
response.contentType = 'text/csv'
response.outputStream << exportResults.getBytes() //getBytes() not portable
response.outputStream.flush()
But this does not trigger any download window in the browser. I was wondering why. I use AngularJS to make a POST request to the controller and resolve the promise as below (JavaScript code):
ExportService.exportData(some_params).then(function(data) {
$log.info('export promise resolved: ');
//window.open(data, '_blank', ''); //not working
}).catch(function(err) {
$scope.message = "failed to retrieve data from export service";
$log.error(err);
}).finally(function(complete) {
$scope.message = "Data export completed.";
});
You need to replace 'text/csv' with 'application/octet-stream' for response.contentType. The content type 'application/octet-stream' is used for a binary file.
Try adding this after response.outputStream.flush() and see if it works.
webRequest.renderView = false
You can even try looking here.

Categories