作为我的第一个Node.js项目,我一直在为我的工作构建一个报告应用程序,人们可以在其中搜索,然后将结果作为CSV下载到他们的计算机上。
为了实现这一点,我一直在使用Socket.IO将JSON数据传递回按钮点击事件的application.js文件。从那里我使用json2csv模块格式化数据。
这是我遇到问题的地方......
我知道Heroku使用临时文件存储(应该没问题,因为我只需要文件在服务器上用于会话,并且添加的清理很好),但是我的文件存在检查回来正面甚至虽然我跑步时看不到文件
heroku run bash
ls
由于我正在使用Socket.IO(据我所知,无论如何),正常的请求和响应回调函数参数不可用。我可以使用data.setHeader()
来设置CSV的标头,这是套接字函数回调而不是response.setHeader()
吗?我是否需要从套接字中分离出该事件监听器并直接从app.get
运行它?
以下是我的代码,它从事件中获取JSON数据并根据我的搜索对其进行格式化:
socket.on('export', function (data) {
jsoncsv({data: data, fields: ['foo', 'bar'], fieldNames: ['Foo', 'Bar']}, function(err, csv) {
if (err) console.log(err);
fs.writeFile('file.csv', csv, function(err) {
if (err) console.log(err);
console.log('File Created');
});
fs.exists('file.csv', function (err) {
if (err) console.log(err);
console.log('File Exists, Starting Download...');
var file = fs.createReadStream('file.csv');
file.pipe(data);
console.log('File Downloaded');
});
});
});
更新
这是我用来构建JSON并将其作为事件发送的实际客户端代码。确切的事件是$('#export').on('click', function () {});
。
server.on('listTags', function (data) {
var from = new Date($('#from').val()), to = new Date($('#to').val()), csvData = [];
var table = $('<table></table>');
$('#data').empty().append(table);
table.append('<tr>'+
'<th>Id</th>' +
'<th>First Name</th>' +
'<th>Last Name</th>' +
'<th>Email</th>' +
'<th>Date Tag Applied</th>' +
'</tr>');
$.each(data, function(i, val) {
var dateCreated = new Date(data[i]['DateCreated']);
if (dateCreated >= from && dateCreated <= to) {
data[i]['DateCreated'] = dateCreated.toLocaleString();
var tableRow =
'<tr>' +
'<td>' + data[i]['ContactId'] + '</td>' +
'<td>' + data[i]['Contact.FirstName'] + '</td>' +
'<td>' + data[i]['Contact.LastName'] + '</td>' +
'<td>' + data[i]['Contact.Email'] + '</td>' +
'<td>' + data[i]['DateCreated'] + '</td>' +
'</tr>';
table.append(tableRow);
csvData.push(data[i]);
}
});
$('.controls').html('<p><button id="export">Export '+ csvData.length +' Records</button></p>');
$('#export').on('click', function () {
server.emit('export', csvData);
});
});
答案 0 :(得分:0)
正如你自己指出的那样,Heroku的文件系统可能有点棘手。
我可以帮助解决您的问题(1),也就是说您没有连接到运行应用程序的相同虚拟机(dyno)。当您运行heroku run bash
时,您将进入一个干净的文件系统,其中包含运行应用程序所需的文件,并且run
命令正在运行(而不是您指定的web
进程在你的Procfile中。)
当您考虑使用Heroku的一个优点是,您可以在需要时轻松地从1个节点扩展到几个节点时,这是有道理的。但是,当您的代码运行10个Web节点时,您仍然期望heroku run bash
以相同的方式工作。那你应该连接哪一个? :)
有关详细信息,请参阅https://devcenter.heroku.com/articles/one-off-dynos#an-example-one-off-dyno。
希望这有帮助。 祝你好运!
/意志
答案 1 :(得分:0)
因此,我们只使用一个http服务器而不是使用socket.io。我有很多代码,因为它部分剥离了我自己的http服务器,当然也应该提供文件(例如你的html,css和js文件)。
var http = require('http'),
url = require('url'),
fs = require('fs'),
path = require('path');
var server = http.createServer(function (req, res) {
var location = path.join(__dirname, url.parse(req.url).pathname),
ext = location.split('.').slice(-1)[0];
if (req.headers['request-action']&&req.headers['request-action'] == 'export'&&req.headers['request-data']) { //this is your export event
jsoncsv({data: req.headers['request-data'], fields: ['foo', 'bar'], fieldNames: ['Foo', 'Bar']}, function(err, csv) {
if (err){
console.log(err);
res.writeHead(404, {'content-type':'text/plain'});
res.end('Error at jsoncsv function: ', err);
return;
}
res.setHeader('content-type', 'text/csv');
var stream = new stream.Writeable();
compressSend(req, res, stream); //this is the equivalent of stream.pipe(res), but with an encoding system inbetween to compress the data
stream.write(csv, 'utf8', function(){
console.log('done writing csv to stream');
});
});
} else {//here we handle normal files
fs.lstat(location, function(err, info){
if(err||info.isDirectory()){
res.writeHead(404, {'content-type':'text/plain'});
res.end('404 file not found');
console.log('File '+location+' not found');
return;
}
//yay, the file exists
var reader = fs.createReadStream(location); // this creates a read stream from a normal file
reader.on('error', function(err){
console.log('Something strange happened while reading: ', err);
res.writeHead(404, {'content-type':'text/plain'});
res.end('Something strange happened while reading');
});
reader.on('open', function(){
res.setHeader('Content-Type', getHeader(ext)); //of course we should send the correct header for normal files too
res.setHeader('Content-Length', info.size); //this sends the size of the file in bytes
//the reader is now streaming the data
compressSend(req, res, reader); //this function checks whether the receiver (the browser) supports encoding and then en200s it to that. Saves bandwidth
});
res.on('close', function(){
if(reader.fd) //we shall end the reading of the file if the connection is interrupted before streaming the whole file
reader.close();
});
});
}
}).listen(80);
function compressSend(req, res, input){
var acceptEncoding = req.headers['Accept-Encoding'];
if (!acceptEncoding){
res.writeHead(200, {});
input.pipe(res);
} else if (acceptEncoding.match(/\bgzip\b/)) {
res.writeHead(200, { 'Content-Encoding': 'gzip' });
input.pipe(zlib.createGzip()).pipe(res);
} else if (acceptEncoding.match(/\bdeflate\b/)) {
res.writeHead(200, { 'Content-Encoding': 'deflate' });
input.pipe(zlib.createDeflate()).pipe(res);
} else {
res.writeHead(200, {});
input.pipe(res);
}
}
function getHeader(ext){
ext = ext.toLowerCase();
switch(ext) {
case 'js': header = 'text/javascript'; break;
case 'html': header = 'text/html'; break;
case 'css': header = 'text/css'; break;
case 'xml': header = 'text/xml'; break;
default: header = 'text/plain'; break;
}
return header;
}
最重要的部分对你很有意思,特别是在第一个if中。在那里它检查标题request-action
是否存在。此标头将包含您的活动名称(例如名称export
)。标头request-data
包含您通过套接字发送的数据。现在您可能还想知道如何管理此客户端:
$('#export').on('click', function () {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'localhost');
xhr.setRequestHeader('request-action', 'export'); //here we set that 'event' header, so the server knows what it should do
xhr.setRequestHeader('request-data', 'csvData); //this contains the data that has to be sent to the server
xhr.send();
xhr.onloadend = function(){//we got all the data back from the server
var file = new Blob([xhr.response], {type: 'text/csv'}); //xhr.response contains the data. A blob wants the data in array format so that is why we put it in the brackets
//now the download part is tricky. We first create an object URL that refers to the blob:
var url = URL.createObjectURL(file);
//if we just set the window.location to this url, it downloads the file with the url as name. we do not want that, so we use a nice trick:
var a = document.createElement('a');
a.href = url;
a.download = 'generatedCSVFile.csv' //this does the naming trick
a.click(); //simulate a click to download the file
}
});
我试图在关键部分添加评论。因为我不知道你目前的知识水平,所以我没有在系统的每个部分都添加评论,但如果有任何不清楚的地方可以随意提问。