Rails download zip from binary data stream - ruby-on-rails

I have a data-API to receive data from the file system. The data-API is a nodeJS server. Our webserver is a Rails server. From the webserver I send parameters to the API which files I want to download. The data-API then zips the request files and sends the zip back as binary data.
So far everything works fine. Now come the part where I'm stuck. I want to present the binary data as a download to the browser. I can either convert the binary data into a zip file and send that to the browser or have another clever solution that will present the binary data as a download.
Here's what I've got so far
Rails webserver side:
app/controllers/simulation_controller.rb
def download_multiple
files = JSON.parse(params[:files])
file_list = #simulation.sweep_points.find(params[:sweep_point_id]).file_list
send_data file_list.zip(files: files), filename: 'archive.zip', type: 'application/zip', disposition: 'attachment'
end
app/models/file_list.rb
def zip
uuid = SecureRandom.uuid
simulation = sweep_point.simulation
files = files[:files].join(" ")
url = URI.parse("http://localhost:3003/files/zip/#{simulation.name}/#{uuid}");
req = Net::HTTP::Get.new(url.to_s)
req.add_field("files", files)
res = Net::HTTP.start(url.host, url.port) { |http| http.request(req) }
content = res.body
content.force_encoding("ASCII-8BIT")
end
nodeJS data-API side:
exports.zip_simulation_files = (req, res) => {
const { headers, method, url } = req;
var simulation = req.params.simulation;
var uuid = req.params.uuid;
var files, command;
req.on("error", (err) => {
console.error(err);
});
files = req.headers.files;
command = "cd postprocess/bumblebee-zip " + "run" + simulation + " " + uuid + " " + "'" + files + "'";
execute(command, (data) => {
res.on("error", (err) => {
console.error(err);
});
const zipFilePath = "/home/samuel/test_bumblebee/.zips/run" + simulation + "-files-" + uuid + ".zip"
var zipFile = fs.readFileSync(zipFilePath);
var stats = fs.statSync(zipFilePath);
res.writeHead(200, {
'Content-Type': 'application/zip',
'Content-Disposition': 'attachment; filename="archive.zip"',
'Content-Length': stats.size,
'Content-Transfer-Encoding': 'binary'
});
res.end(zipFile, 'binary');
});
}
So far I'm getting back a response that is a string which seems to be a binary string. Which starts and end with this:
"PK\x03\x04\x14\x00\x00\x00\b\x00\xA8\x89\xDBJ\xB1\xB8\xA1\xBF2\x03\x00\x00\xB7F\x00\x006\x00\x1C\x00runZhangD3FINAL/13.0V/annihilation_abs_profile_001.outUT\t\x00\x0 ....... x06\x00\x00\x00\x00\x05\x00\x05\x00l\x02\x00\x00)\x10\x00\x00\x00\x00"
I've looked into different solutions in trying to turn the binary data string into a zip file or presenting the stream directly to the browser but nothing worked. Either the zip is not created or the binary was not seen as proper data.
What would be a good solution?

Related

IOS Safari issue of download file name is "Unknown"

I implemented a Jersey REST service to download the zip file.
Now, I would like to use axios in front end to download the zip file.
Everything is fine in PC Chrome but when tried with Safari on iPad it opens a tab with name "unknown".
I have searched some articles and mentioned that this may related to IOS safari compatibility.
e.g. https://caniuse.com/#feat=download
However, I also want know if there is any method to show the downloaded file as "file.zip" for safari.
Below is my code
Backend:
#GET
#Path("/getTestingReport")
#Produces("application/zip")
public Response getTestingReport() throws Exception {
// set file (and path) to be download
File file = new File("C:/Users/abc/Desktop/test.zip");
ResponseBuilder responseBuilder = Response.ok((Object) file);
responseBuilder.header("Content-Disposition", "attachment; filename=\"MyJerseyZipFile.zip\"");
return responseBuilder.build();
}
Frontend:
axios.get("report/getTestingReport").then((response) => {
console.log("response", response)
var blob = new Blob([response.data], { type: "application/zip" });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'file.zip');
document.body.appendChild(link);
link.click();
}).catch((error) => {
console.error("error response", error.response)
});
May I have any suggestion?

Unable to post to aspnet webapi using angular http

I have created a simple webapi controller.
// POST request api to get a string base64 image, store it and returns its name.
public string Post([FromBody]string image)
{
if (image == null)
return "No image sent";
// Generating random file name using the current date and time and random text
string fileName = "image-" + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss") + Path.ChangeExtension(
Path.GetRandomFileName(),
".jpg"
);
// If the directory does not exist create a new one
if (!Directory.Exists(#".\uploads\"))
{
DirectoryInfo DI = Directory.CreateDirectory(#".\uploads\");
}
File.WriteAllBytes(#".\uploads\" + fileName, Convert.FromBase64String(image));
return "Submitted as File: " + fileName;
}
When I send a post request through postman, it works as fine and returns the string "Submitted as File ..."
But when I try it in Angular I get the following error.
OPTIONS http://example.com/Api/Image/Index 405 (Method Not Allowed)
My angular service:
getResult(base64image) {
//Where base64image is a string.
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post('http://example.com/Api/Image/Index', base64image, options)
.map(res => res);
}
I know that it has something to with cors. But it is just a simple api.

How to get the object url with alias name from aws s3 using CloudFront

I am uploading files with unique id like 'd9127dfd01182afe7d34a37' as object name to amazon s3 and storing the file information with my local database including original name of the file. And I am using CloudFront url to download the file.
If I download the file using CloudFront url file name is d9127dfd01182afe7d34a37. But I need to change file name again to it's original name wich I have in my database. I don't want to download it. I want to give the url with original name to the client(WebUI) and client can download it through url.
serverside code
document_url = initialize_cloud_service(document.provider['primary']).get_object_url(document_id, expires_at, 'CloudFront' )
if document_url
item = {}
item['id'] = document['_id'].to_s
item['name'] = document['name']
item['mime_type'] = document['mime_type']
item['url'] = document_url
return {success: true, message: MESSAGES['get_url_succuss'],data: item}.to_json
end
client side code
download: function(response){
file = response.data
link = document.createElement('a');
link.download = file.name;
link.href = file.url;
link.click();
},
Is there any way to achieve this? Please help me out. I am using ruby on rails and mongodb as local database.
Thanks
I have achieved by doing the following changes
Server Side code
begin
expires_at = Time.now.to_i + 30.seconds.to_i
options = nil
selected_provider = provider || document.provider['primary']
case selected_provider
when "s3"
options = {"response-content-disposition" => "attachment; filename=#{document['name']}"}
downloadable_url = initialize_cloud_service(selected_provider).get_downloadable_url(document_id, expires_at, options)
when "google_cloud"
downloadable_url = initialize_cloud_service(selected_provider).get_downloadable_url(document_id, expires_at, options)
downloadable_url += "&response-content-disposition=attachment%3B%20filename%3D#{document['name']}"
end
item = {}
item['id'] = document['_id'].to_s
item['name'] = document['name']
item['mime_type'] = document['mime_type']
item['url'] = downloadable_url
return {success: true, message: MESSAGES['get_url_succuss'],data: item}.to_json
rescue Exception => e
puts 'Exception in download, message: ' + e.message
return {success: false, message: MESSAGES['default']}.to_json
end
client side code
download: function(response){
var hiddenIFrameID = 'hiddenDownloader',
iframe = document.getElementById(hiddenIFrameID);
if (iframe === null) {
iframe = document.createElement('iframe');
iframe.id = hiddenIFrameID;
iframe.style.display = 'none';
document.body.appendChild(iframe);
}
iframe.src = response.data.url;
},

Titanium Studio to Grails imge post

I am trying to POST an image to my grails application and I'm not having much luck.
My titanium code is:
function upload(){
var xhr = Titanium.Network.createHTTPClient();
xhr.onerror = function(e){
Ti.API.info(picMedia + " : " +message.value);
Ti.API.info('IN ERROR ' + e.error);
alert('Sorry, we could not upload your photo! Please try again.');
};
xhr.onload = function(){
Ti.API.info('IN ONLOAD ' + this.status + ' readyState ' + this.readyState);
};
xhr.onsendstream = function(e){
Ti.API.info('ONSENDSTREAM - PROGRESS: ' + e.progress);
};
// open the client
xhr.open('POST', 'http://localhost:8080/FYP/Profile/appUploader');
// send the data
xhr.send({
media: picMedia,
message: message.value,
});
}
My grails code is as follows:
def appUploader(){
println "MEDAI PARAMS: " + params.media
def f = request.getFile('media') ;
println "HERE: " + f
if (request.getFile(params.media).getOriginalFilename()) {
println "FROM APP: " + request.getFile('myFile').getOriginalFilename()
return
}
}
Im getting error from the mobile app and error on the "if" line in the web app.
What am i doing wrong?
we had the same problem in one of our apps. The difficulty is that titanium is not really able to handle binary files in that case.
We did the following:
create base64 encoded string of the image on client side
post this string to the backend
decode base64 to image again
We analyzed a lot of network traffic and in most cases titanium tries to send the file but due to javascript its alway converted into some kind of ascii and this is not usable by the server side.

How to export pdf report in jasper reports

I want to export a report as pdf and it should ask the user for a download location. How do I do this in grails?
This is my code:
def exportToPdf(JasperPrint jasperPrint,String path,request){
String cur_time =System.currentTimeMillis();
JRExporter pdfExporter = null;
pdfExporter = new JRPdfExporter();
log.debug("exporting to file..."+JasperExportManager.exportReportToPdfFile(jasperPrint, "C:\\pdfReport"+cur_time+".pdf"));
return ;
}
In jasper controller:
/**
* Generate a html response.
*/
def generateResponse = {reportDef ->
if (!reportDef.fileFormat.inline && !reportDef.parameters._inline) {
response.setHeader("Content-disposition", "attachment; filename=\"" + reportDef.name + "." + reportDef.fileFormat.extension + "\"");
response.contentType = reportDef.fileFormat.mimeTyp
response.characterEncoding = "UTF-8"
response.outputStream << reportDef.contentStream.toByteArray()
} else {
render(text: reportDef.contentStream, contentType: reportDef.fileFormat.mimeTyp, encoding: reportDef.parameters.encoding ? reportDef.parameters.encoding : 'UTF-8');
}
}
Have you looked at the Jasper Plugin? It seems to have the tools already built for you. As far as asking the user for a download location the browser has some controller over how files are received from a web page. Is your real issue that you want control over the download location?
[UPDATE]
Using the location 'c:\' is on your server not the client and this is why it is not downloading.
try something like this...
def controllerMethod = {
def temp_file = File.createTempFile("jasperReport",".pdf") //<-- you don't have to use a temp file but don't forget to delete them off the server at some point.
JasperExportManager.exportReportToPdfFile(jasperPrint, temp_file.absolutePath));
response.setContentType("application/pdf") //<-- you'll have to handle this dynamically at some point
response.setHeader("Content-disposition", "attachment;filename=${temp_file.getName()}")
response.outputStream << temp_file.newInputStream() //<-- binary stream copy to client
}
I have not tested this and there are better ways of handling the files and streams but i think you'll get the general idea.

Resources