We are implementing file download function.
UI, service API server, and file server (external server) exist, and I cannot access the file server. (Code cannot be changed)
Currently, a file is downloaded by requesting UI -> FileServer.
I want to configure UI -> Service API -> File Server for multiple file downloads and exception handling.
As the service API operates in the middle, the final value of http that the client receives is different, but I don't know why.
Existing file servers returned an ArrayBuffer,
It was changed to a long string in String format while going through my service API server.
// my Server -> fileServer (GET Http Request)
// setting header...
// setting uri...
HttpEntity req = new HttpEntity<>(httpHeaders);
RestTemplate rt = new RestTemplate();
HttpEntity<byte[]> result = rt.exchange(
uri,
HttpMethod.GET,
req,
byte[].class
);
byte[] blob = result.getBody();
The reason why I received byte[] through result.getBody() is that I thought that the return value of the file server would be a file, and it would be a byte[] type in Java.
// react (client code)
// blobFromApi = The result my server responded
let blob = new Blob([blobFromApi], { type:'image/png' });
if (navigator.userAgent.match('CriOS')) {
let reader = new FileReader();
reader.onload = function (e) {
window.location.href = reader.result;
};
reader.readAsDataURL(blob);
} else {
FileSaver.saveAs(blob, file.file_name);
}
});
In conclusion, is it possible to implement a file download function in my current situation?
Related
I have an ng-click implementation that does a $http.post() call to the server which returns a file content. so, a file download happens on the browser. below is the code snippet:
$scope.downloadFile = function(mediaObj) {
var downloadReq = {
"cid": $cId,
"pid":$pId,
"mid":$mId
};
$http.post(<server url>, downloadReq)
.then(function(response) {
var downloadUrl = URL.createObjectURL(new Blob([response.data]));
var a = document.createElement('a');
a.href = downloadUrl;
a.target = '_blank';
a.download = response.headers('Content-Disposition').split(';')[1].trim().split('=')[1];
document.body.appendChild(a);
a.click();
a.remove();
}, function(response) {
//unable to download
$scope.downloadErr = true;
});
}
server side code snippet is like this:
public void handleDownloadRequest(String json, HttpServletRequest request, HttpServletResponse httpServletResponse) {
....
// make rest call to another microservice to get file content
IOUtils.copyLarge(new ByteArrayInputStream((byte[])response.getBody()), httpServletResponse.getOutputStream());
// copy all headers from rest call response to httpServletResponse
httpServletResponse.flushBuffer();
}
after this, the next call to server (it need not be download itself, any other server call) is getting a brand new session. the old session has been destroyed.
because of this, server side client session state is lost and working on the client is messed up.
can anyone please help me with understanding why a new session is getting created after the download operation? how i can avoid this? why is the old session getting destroyed?
Thanks in advance.
I am answering my own question. Maybe it will save someone's time. As part of the handleDownloadRequest() method, i was making a rest call to another microservice to get the file data. the httpresponse of that rest call had a new session id that was also getting copied into the httpServletResponse of the handleDownloadRequest() method.
this was getting propagated to the client and in turn the client session state was lost.
SOLUTION: i removed the session cookie header from the response while copying over the headers.
Take care of the http response while making internal rest calls...
I'm implementing file download using AngularJS and WCF. My back-end is a .NET project hosted in IIS. The file is serialized as an array of bytes and then on the client side I utilize the File API to save the content.
To simplify the problem, back-end is like:
[WebInvoke(Method = "GET", UriTemplate = "FileService?path={path}")]
[OperationContract]
public byte[] DownloadFileBaseOnPath(string path)
{
using (var memoryStream = new MemoryStream())
{
var fileStream = File.OpenRead(path);
fileStream.CopyTo(memoryStream);
fileStream.Close();
WebOperationContext.Current.OutgoingResponse.Headers["Content-Disposition"] = "attachment; filename=\"Whatever\"";
WebOperationContext.Current.OutgoingResponse.ContentType = "application/octet-stream"; // treat all files as binary file
return memoryStream.ToArray();
}
}
And on client side, it just sends a GET request to get those bytes, converts in into a blob and save it.
function sendGetReq(url, config) {
return $http.get(url, config).then(function(response) {
return response.data;
});
}
Save the file then:
function SaveFile(url) {
var downloadRequest = sendGetReq(url);
downloadRequest.then(function(data){
var aLink = document.createElement('a');
var byteArray = new Uint8Array(data);
var blob = new Blob([byteArray], { type: 'application/octet-stream'});
var downloadUrl = URL.createObjectURL(blob);
aLink.setAttribute('href', downloadUrl);
aLink.setAttribute('download', fileNameDoesNotMatter);
if (document.createEvent) {
var event = document.createEvent('MouseEvents');
event.initEvent('click', false, false);
aLink.dispatchEvent(event);
}
else {
aLink.click();
}
setTimeout(function () {
URL.revokeObjectURL(downloadUrl);
}, 1000); // cleanup
});
}
This approach works fine with small files. I could successfully download files up to 64MB. But when I try to download a file larger than 64MB, the response.body is empty in Chrome. I also used Fiddler to capture the traffic. According to Fiddler, Back-end has successfully serialized the byte array and returned it. Please refer to the screenshot below.
In this example, I was trying to download a 70MB file:
And the response.data is empty:
Any idea why this is empty for file over 70MB? Though the response itself is more than 200MB, I do have enough memory for that.
Regarding to the WCF back-end, I know I should use Stream Mode when it comes to large files. But the typical use of my application is to download files less than 10MB. So I hope to figure this out first.
Thanks
Answer my own question.
Honestly I don't know what's going wrong. The issue still persists if I transfer it as a byte array. I eventually gave up this approach by returning a stream instead. Then on the client side, adding the following configuration
{responseType : blob}
and save it as a blob.
I'm looking for a solution where I can upload any file to SQL server from an AngularJS frontend to .Net Web Api 2 and straight to SQL Server Database. I've done some research and for angularjs i'm mainly looking at ng-file-upload. my problem is most of the solutions that i've looked at saves the file into a temp folder. I'm not sure if it's possible but I want it straight to an SQL server table.
I've seen some solutions where it converts the file into a byte array which can be saved to an SQL table but I'm not sure how to do this in a .NET web api 2 and from an angularjs front end. thank you in advance.
Don't save files to SQL server--that's not what it's for. See this answer: In MVC4, how do I upload a file (an image) to SQL Server that's part of my domain model? And this answer: Storing files in SQL Server
Uploading files in angular is easy. Do it like this:
Controller
$scope.uploadFile = function() {
//get the filename from the <input type='file'>
//angular doesn't allow attaching ngModel to file input
var fileInput = document.getElementById("myInputId");
//check if there's a file
if(fileInput.files.length === 0) return;
//you cannot send a file as JSON because json is in the string format
//for fileuploads, you must send as a FormData() object
//C# accepts HttpPostedFileBase as the file argument
var file = fileInput.files[0];
//put the file in a new formdata object
var payload = new FormData();
payload.append("file", file);
//upload file to C# controller
$http.post("path/to/C#/controller", payload, {
//you **need** to specify these options, without them upload does not work
transformRequest: angular.identity,
headers: { "Content-Type": undefined }
}).then(function(data) {
//success
}, function(error) {
//error
});
}
C#/ASP.NET
[WebMethod]
public string UploadFile(HttpPostedFileBase file) {
//access the file object here
var inputStream = file.InputStream;
var fileName = Path.GetFileName(file.FileName);
try
{
file.SaveAs("local/path" + fileName);
}
catch (IOException exc)
{
return "Error: " + exc.Message;
}
return "success";
}
A server side application requires authorization on file download links. This means a normal <a ng-href="{{createLinkToFile()}}"> is no longer sufficient to get enough parameters passed to the server.
When trying to use a programmatic call to the file download, I get the response data back to Dart client application. Using a simple http GET:
var url = "http://example.com/file";
headers.putIfAbsent("Authorization", () => "bearer " + token;
_http.get(url: url, headers : headers);
The future returned by the GET will hold the data, but how do I instruct the browser to download it as a file, instead of just trying to keep it in memory?
Or is there a way to just do it in a normal link?
After downloading the data from the server like shown in Using Dart to Download a PNG File (Binary File) and displaying it not working you can create a download link like shown at http://blog.butlermatt.me/2014/03/dynamically-generating-download-files/
import 'dart:html';
void main() {
List body = [ 'Some test data ...\n'];
// Create a new blob from the data.
Blob blob = new Blob(body, 'text/plain', 'native');
// Create a data:url which points to that data.
String url = Url.createObjectUrlFromBlob(blob);
// Create a link to navigate to that data and download it.
AnchorElement link = new AnchorElement()
..href = url
..download = 'random_file.txt'
..text = 'Download Now!';
// Insert the link into the DOM.
var p = querySelector('#text');
p.append(link);
}
The code of Seth solves indeed part of the problem. To make it a bit more complete, I'm now using the following:
void doPdfFileRequest(String url) {
var request = new HttpRequest();
request.open('GET', url);
request.responseType = "blob";
request.withCredentials = false;
request.setRequestHeader("Accept", _httpAcceptHeader);
request.setRequestHeader("Authorization", "bearer " + token);
request.onReadyStateChange
.listen((r) => onData(request, "filename.pdf"));
request.send();
}
void onData(HttpRequest request, String filename) {
if (request.readyState == HttpRequest.DONE && request.status == 200) {
if (!isIE()) {
var contentType = request.getResponseHeader("content-type");
AnchorElement downloadLink = new AnchorElement(
href: Url.createObjectUrlFromBlob(request.response));
downloadLink.rel = contentType;
downloadLink.download = filename;
var event = new MouseEvent("click", view: window, cancelable: false);
downloadLink.dispatchEvent(event);
} else {
var href = Url.createObjectUrlFromBlob(request.response);
window.open(href, "_self");
}
}
}
A few things to notice. Instead of using the downloadLink.click(), a mouse event is constructed to ensure that it works on Firefox as well as on Safari and Chrome. Firefox seems not to handle the click() otherwise. Binding it to the DOM as is done in the code of Seth isn't necessary.
Internet Explorer doesn't understand the download attribute, so nothing will happen, therefore a window.open is used to at least have it work (though not ideal) on IE, it's redirecting to self to avoid being hit by the pop up blocker.
There are solutions that convert the result download result to Base64 first and put it in a data:mimetype href, using the blob this isn't necessary.
A nice way to set the filename on the file to download would be through the content disposition header, but this header is marked as unsafe, so cannot be used. The filename is now set in the code.
Another note, notice that a HttpRequest is used instead http.get(), The HttpRequest allows you to set the responseType, in this case blob, which can be transformed into a object url.
I am creating a csv file with node js and now want to download it to user's browser's default download location. We are using seneca with nodejs and csv file is getting saved on the server. Now when use will click export on the front end which angular based, node will create a csv and download that to user machine. How can we achieve this?
It is possible to download dynamic files generated with Node.js to the browser's default download location. There are many posts outlining how to retrieve static files from the server using the Express helper res.download(). Specific to your question there is a way to achieve what you are asking.
With interpretation of your question the following process is followed:
User generated data is sent to the server for processing when a user clicks export.
Node processes the data and generates a file that is to be downloaded without a second user interaction (user clicks Export and the file is downloaded).
Client
//Export button
$("#exportBtn").click(function () {
//Code to generate data for the CSV and save it to the src variable
var src = csvData;
//Send the CSV data to Node for processing and file generation.
$.post("http://localhost:3000/submitcsv", { csv: src }, function (data) {
//Check the returned file name in data - this check depends on your needs; ie: regex
if (data) {
//request the file that is to be downloaded to the client. Optionally use $window.open()
window.location.href = "http://localhost:3000/app/" + data;
}
});
});
Server
//Post data from the client
app.post('/submitcsv', function (req, res) {
var async = require('async');
var fs = require('fs');
var path = require('path');
var csvData = req.body.csv;
function processCSV(callback) {
//Code to create the csv file and a uniqueIdentifier
callback();
}
function finalize() {
//Save the CSV to the server. This is a specific location on the server in /app.
//You can use Express.static paths that suit your setup.
fs.writeFile('./app/temp/csvFile' + uniqueIdentifier + '.csv', buf, function (err) {
if (err) {
console.log(err);
res.send(500, "Something went wrong...");
}
else {
console.log("CSV file is saved");
//Send the file name and location back to the client
res.send('/temp/csvFile' + uniqueIdentifier + '.csv');
}
});
}
// After the CSV data is processed, save the file and send it to the client.
async.series([
processCSV
], finalize);
});
//Send the requested file back to the client
app.get('./app/:csvFile', function (req, res){
var c = req.params.csvFile;
res.download(c);
//Code to delete the file if it is temporary via fs.unlink
});
While it is not shown in the above code for simplicty, it is recommended that the /app routes sending and receiving this type of data are secured in some manner. Using Passport OIDCStrategy for the protected routes will look something like:
app.all('/app/*', function(req, res, next) {
//Check that the user has previously been authenticated
if (req.user){
next();
} else {
// require the user to log in
res.redirect("/login");
}
});
I dont think you can... Nodejs is serverside... so anything you put under Nodejs, will go to the server...
If the USER click on some button or some other event triggers a process that generates the CSV file, then the USER will have to choose where they want to save it or the file will be downloaded automatically to the default Downloads directory that was specified under USER's browser settings...