Upload a file from dart client to Node server - node.js

I'm stuck with this issue : I can't get my upload to work:
This is a node.js code taht works with a standard <form><input type="file" name="toUpload/>
router.post('/sp/file', function (req, res) {
// File to be uploaded
console.log("###" + req.files);
var fileToUpload = req.files.toUpload;
//console.log(fileToUpload);
var dir = __dirname + "/files";
/* var dir = __dirname + "/files/" + Date.now();
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}*/
fileToUpload.mv( __dirname + "/files/" + fileToUpload.name, function (err) {
if (err) {
console.log("error: " + err);
} else
console.log("upload succeeded");
console.log(fileToUpload);
console.log(__dirname + "/files/" + fileToUpload.name);
uploadFilesStorj.uploadFile(__dirname + "/files/" + fileToUpload.name);
});
});
Now, when I try to upload a file through dart, I get stuck since the sent data is not in the same format:
class AppComponent {
void uploadFiles(dynamic files) {
if (files.length == 1) {
final file = files[0];
final reader = new FileReader();
//reader.onProgress.listen()
reader.onLoad.listen((e) {
sendData(reader.result);
});
reader.readAsDataUrl(file);
}
}
sendData(dynamic data) async {
final req = new HttpRequest();
req.onReadyStateChange.listen((Event e) {
if (req.readyState == HttpRequest.DONE &&
(req.status == 200 || req.status == 0)) {}
});
req.onProgress.listen((ProgressEvent prog) {
if (prog.lengthComputable)
print("advancement : " + (prog.total / prog.loaded).toString());
else
print("unable to compute advancement");
});
req.open("POST", "/sp/file");
req.send(data);
}
}
here's my dart angular front code
<input type="file" #upload (change)="uploadFiles(upload.files)"
(dragenter)="upload.style.setProperty('border', '3px solid green')"
(drop)="upload.style.setProperty('border', '2px dotted gray')" class="uploadDropZone" name="toUpload"/>
The data sent by this method is in the form :
Request payload:
data:text/html;base64,PGh0bWw+DQogICA8aGVhZD4NCiAgICAgIDx0aXRsZT5GaWxlIFVwbG9hZGluZyBGb3JtPC9
I passed a lot of time on it without success, can anybody help please

I finally found a way to post it as a multi-part form:
void uploadFiles() {
var formData = new FormData(querySelector("#fileForm"));
HttpRequest.request("/sp/file", method: "POST", sendData: formData).then((req) {
print("OK");
});
}
is used in conjunction with
<form id="fileForm">
<input type="file" #upload (change)="uploadFiles(upload.files)"
(dragenter)="upload.style.setProperty('border', '3px solid green')"
(drop)="upload.style.setProperty('border', '2px dotted gray')" class="uploadDropZone" name="toUpload"/>
</form>

You are writing the file content directly in the request body in the Dart variant. However the HTML form sends a request with multipart form encoding and embeds the file in there. That's also what your server expects.

Related

Saving blobs as a single webm file

I'm recording the users screen via webrtc, and then posting video blobs every x seconds using MediaStreamRecorder. On the server side I have an action set up in sails which saves the blob as a webm file.
The problem is that I can't get it to append the data, and create one large webm file. When it appends the file size increases like expected, so the data is appending, but when I go to play the file it'll either play the first second, not play at all, or play but not show the video.
It would be possible to merge the files with ffmpeg, but I'd rather avoid this if at all possible.
Here's the code on the client:
'use strict';
// Polyfill in Firefox.
// See https://blog.mozilla.org/webrtc/getdisplaymedia-now-available-in-adapter-js/
if (typeof adapter != 'undefined' && adapter.browserDetails.browser == 'firefox') {
adapter.browserShim.shimGetDisplayMedia(window, 'screen');
}
io.socket.post('/processvideo', function(resData) {
console.log("Response: " + resData);
});
function handleSuccess(stream) {
const video = document.querySelector('video');
video.srcObject = stream;
var mediaRecorder = new MediaStreamRecorder(stream);
mediaRecorder.mimeType = 'video/webm';
mediaRecorder.ondataavailable = function (blob) {
console.log("Sending Data");
//var rawIO = io.socket._raw;
//rawIO.emit('some:event', "using native socket.io");
io.socket.post('/processvideo', {"vidblob": blob}, function(resData) {
console.log("Response: " + resData);
});
};
mediaRecorder.start(3000);
}
function handleError(error) {
errorMsg(`getDisplayMedia error: ${error.name}`, error);
}
function errorMsg(msg, error) {
const errorElement = document.querySelector('#errorMsg');
errorElement.innerHTML += `<p>${msg}</p>`;
if (typeof error !== 'undefined') {
console.error(error);
}
}
if ('getDisplayMedia' in navigator) {
navigator.getDisplayMedia({video: true})
.then(handleSuccess)
.catch(handleError);
} else {
errorMsg('getDisplayMedia is not supported');
}
Code on the server:
module.exports = async function processVideo (req, res) {
var fs = require('fs'),
path = require('path'),
upload_dir = './assets/media/uploads',
output_dir = './assets/media/outputs',
temp_dir = './assets/media/temp';
var params = req.allParams();
if(req.isSocket && req.method === 'POST') {
_upload(params.vidblob, "test.webm");
return res.send("Hi There");
}
else {
return res.send("Unknown Error");
}
function _upload(file_content, file_name) {
var fileRootName = file_name.split('.').shift(),
fileExtension = file_name.split('.').pop(),
filePathBase = upload_dir + '/',
fileRootNameWithBase = filePathBase + fileRootName,
filePath = fileRootNameWithBase + '.' + fileExtension,
fileID = 2;
/* Save all of the files as different files. */
/*
while (fs.existsSync(filePath)) {
filePath = fileRootNameWithBase + fileID + '.' + fileExtension;
fileID += 1;
}
fs.writeFileSync(filePath, file_content);
*/
/* Appends the binary data like you'd expect, but it's not playable. */
fs.appendFileSync(upload_dir + '/' + 'test.file', file_content);
}
}
Any help would be greatly appreciated!
I decided this would be difficult to develop, and wouldn't really fit the projects requirements. So I decided to build an electron app. Just posting this so I can resolve the question.

What is the best way to multiple file upload through a single input in nodejs express 4?

I'm using nodejs with express 4. I'm trying to upload multiple file through a single input field. Also I submit my form through ajax.
I'm using express-fileupload middleware for uploading files. When i upload multiple files, it works fine. But when upload a single file, it's not working.
html code-
<input type="file" name="attach_file" id="msg_file" multiple />
ajax code-
var data = new FormData();
$.each($('#msg_file')[0].files, function(i, file) {
data.append('attach_file', file);
});
$.ajax({
url: '/send_message',
data: data,
cache: false,
contentType: false,
processData: false,
method: 'POST',
type: 'POST',
success: function(data){
console.log(data);
}
});
server side js code-
router.post('/send_message', function(req, res, next){
if (!req.files)
res.json('No files were uploaded.');
let sampleFile = req.files.attach_file;
console.log(sampleFile.length);
var file_info = [];
var count = 0;
sampleFile.forEach(function(ele, key) {
ele.mv(path.resolve(`./public/upload/${ele.name}`), function(err) {
if (err){
console.log(err);
}else{
file_info.push(ele.name);
}
count++;
if(sampleFile.length == count){
res.json({file_name: file_info });
}
});
});
});
if i upload a single file console.log(sampleFile.length); show undefined.
After different kind of testing, I found the issue. Everything is ok except when I upload a single file.
When ajax send a single file, it was not an array. That's why, length was undefined and forEach did not run. Need to check first, then use mv() function, like as-
if(sampleFile instanceof Array){
// here is the forEach block
}else{
// run a single mv() function
}
Another complete example and also late answer; in case that help someone.
this is not ajax-based; I used a form in client-side; but you can send data using JS and XHR.
It supports both single and also multiple uploads.
upload.js
'use strict';
const fss = require('fs')
const pth = require('path');
const exp = require('express');
const swg = require('swig');
const efm = require("formidable");
const app = exp();
const thm = swg.compileFile(pth.join(__dirname, '', 'upload.html'));
app.listen(9009);
app.get(`/`, async (q, r) => r.send(thm({ msg: "Select a File to Upload" })));
app.get(`/:msg`, async (q, r) => r.send(thm({ msg: q.params.msg })));
app.post('/upload', (r, q) => {
const form = efm({ multiples: true });
form.parse(r, (e, p, files) => {
let dir = pth.join(__dirname, '', '/media/');
if (!fss.existsSync(dir)) fss.mkdirSync(dir);
let uploaded = 0, exists = 0, error = 0;
//if single file uploaded
if(!Array.isArray(files.file)){
files.file=[files.file]
}
files.file.forEach(f => {
let nPth = dir + f.name;
try {
fss.accessSync(nPth, fss.F_OK);
exists++;
} catch (file_e) {
let err = fss.renameSync(f.path, nPth);
if (err) error++; else uploaded++;
}
});
q.redirect(`Upoader -> All: ${files.file.length}, Uploaded: ${uploaded}, Existed: ${exists}, Error: ${error}`);
});
});
**Its better use "A-Sync" functions.
**I think its also better if you run uploader script on another port.
upload.html
<h3>{{msg}}</h3>
<br/>
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" multiple>
<input type="submit">
</form>

Angular download file from byte array sent from node.js

I think I'm very close to what I want to do. I have the following api get method in node.js that is retrieving a file varbinary(MAX) from an SQL Server database. It was converted from a base64 encoded string before inserted so the Content Type information was stripped from the string.
node.js
router.get('/getFile', (req, res) => {
console.log("Calling getFile for file " + req.query.serialNumber + ".")
var serialNumber = req.query.serialNumber;
let request = new sql.Request(conn);
request.query('SELECT FileName + \'.\' + FileExtension AS \'File\', FileType, ContentType, SerialNumber, Chart ' +
'FROM dbo.ChangeFiles ' +
'WHERE SerialNumber = ' + serialNumber)
.then(function (recordset) {
log("Successfully retrieved file " + recordset[0].SerialNumber + " from database.");
log("Length of blob " + recordset[0].File + " is " + recordset[0].Chart.length)
res.status(200);
res.setHeader('Content-Type', recordset[0].ContentType);
res.setHeader('Content-Disposition', 'attachment;filename=' + recordset[0].File);
res.end(Buffer.from((recordset[0].Chart)));
}).catch(function (err) {
log(err);
res.status(500).send("Issue querying database!");
});
});
That works fine, but what to do in Angular to retrieve it and prompt for a download for the user has not been clear for me, nor has there been a lot as far as help/resources online. Here is what I have so far in my service class.
fileDownload.service.ts
downloadFile(serialNumber: string): Observable<any> {
return this.http.get(this.baseURL + '/getFile', { params: { serialNumber: serialNumber } })
.map(this.extractFile);
}
private extractFile(response: Response) {
const file = new Blob([response.blob]);
FileSaver.saveAs(file);
// const url = window.URL.createObjectURL(file);
// window.open(url);
return file;
}
As you can see I've tried a couple of approaches. The commented out portion of the extractFile method didn't work at all, and using the FileSaver.saveAs function produces a file download of an unknown type, so the headers sent from node.js didn't seem to affect the file itself.
Would someone be able to advise how to proceed in Angular with what is successfully being sent from node.js so that I can successfully download the file, regardless of type?
Thanks so much in advance.
I got it working afterall. I had to rework the api call so that it sent all of the file information separately so that the MIME type, and file name can be assigned to the file on the client side in the component class. For some reason when I tried to do so all in the api, it wouldn't work so that was my work around. So here is what works for me.
node.js api
router.get('/getFile', (req, res) => {
console.log("Calling getFile for file " + req.query.serialNumber + ".")
var serialNumber = req.query.serialNumber;
let request = new sql.Request(conn);
request.query('SELECT FileName + \'.\' + FileExtension AS \'File\', FileType, ContentType, SerialNumber, Chart ' +
'FROM dbo.ChangeFiles ' +
'WHERE SerialNumber = ' + serialNumber)
.then(function (recordset) {
log("Successfully retrieved file " + recordset[0].SerialNumber + " from database.");
log("Length of blob " + recordset[0].File + " is " + recordset[0].Chart.length)
res.send(recordset[0]);
}).catch(function (err) {
log(err);
res.status(500).send("Issue querying database!");
});
});
component class
downloadFile(serialNumber: string): void {
this.changeService.downloadFile(serialNumber).subscribe((res: any) => {
const ab = new ArrayBuffer(res.Chart.data.length);
const view = new Uint8Array(ab);
for (let i = 0; i < res.Chart.data.length; i++) {
view[i] = res.Chart.data[i];
}
const file = new Blob([ab], { type: res.ContentType });
FileSaver.saveAs(file, res.File);
console.log(res);
});
}
service class
downloadFile(serialNumber: string): Observable<any> {
return this.http.get(this.baseURL + '/getFile', { params: { serialNumber: serialNumber } })
.map(this.extractFile);
}
private extractFile(response: Response) {
// const file = new Blob([response.blob]);
// FileSaver.saveAs(file);
// const url = window.URL.createObjectURL(file);
// window.open(url);
const body = response.json();
return body || {};
}
Update your code to call subscribe instead of map

Put data and attachment to Cloudant DB IBM bluemix

I am trying to PUT my data within its attachment. I am doing it with NodeJS
Here is my code:
var date = new Date();
var data = {
name : obj.name,
serving : obj.serving,
cuisine : obj.cuisine,
uploadDate : date,
ingredients : obj.ing,
directions: obj.direction
} //Assume that I read this from html form and it is OK
db.insert(data, function(err, body){
if(!err){
console.log(body);
var id = body.id
var rev = body.rev
var headers = {
'Content-Type': req.files.image.type
};
var dataString = '#' + req.files.image.path;
var options = {
url: 'https://username:password#username.bluemix.cloudant.com/db_name/' + id + '/' + req.files.image.name +'?' + 'rev=' + rev,
method: 'PUT',
headers : headers,
body : dataString
}
console.log(options)
function callback(error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body);
}
}
request(options, callback);
}
});
I am getting a 201 response after an image attachment has been sent. But in the cloudant dashboard I see "length": 38 of uploaded image which is impossible.
If I try to access uploaded image it gives:
{"error":"not_found","reason":"Document is missing attachment"}.
How I can fix this problem?
It looks like you are uploading the path to the image and not the contents of the image itself:
var dataString = '#' + req.files.image.path;
This will just upload the string '#xyz' where xyz is the path of the file. You need to upload the contents of the image. See the following for more information:
https://wiki.apache.org/couchdb/HTTP_Document_API#Attachments
I am not sure how to get the contents of the uploaded file from req.files. I believe req.files no longer works in Express 4, so I use multer:
https://github.com/expressjs/multer
This is how I upload a file to Cloudant that was uploaded to my app:
Client
<form action="/processform" method="post" enctype="multipart/form-data">
<input type="file" name="myfile">
<input type="submit" value="Submit">
</form>
Node.js Routing
var multer = require('multer');
...
var app = express();
...
var uploadStorage = multer.memoryStorage();
var upload = multer({storage: uploadStorage})
app.post('/processform', upload.single('myfile'), processForm);
Note: 'myfile' is the name of the file input type in the form.
Node.js Upload to Cloudant
function processForm() {
// insert the document first...
var url = cloudantService.config.url;
url += "/mydatabase/" + doc.id;
url += "/" + encodeURIComponent(req.file.originalname);
url += "?rev=" + doc.rev;
var headers = {
'Content-Type': req.file.mimetype,
'Content-Length': req.file.size
};
var requestOptions = {
url: url,
headers: headers,
body: req.file.buffer
};
request.put(requestOptions, function(err, response, body) {
if (err) {
// handle error
}
else {
// success
}
});
...
}

Express / torrent-stream: writing a file from stream, sending socket to client with url but client cannot find file

I'm working on a personal project which basically takes a magnet link, starts downloading the file and then renders the video in the torrent in the browser. I'm using an npm module called torrent-stream to do most of this. Once I create the readable stream and begin writing the file I want to send a socket message to the client with the video url so that the client can render a html5 video element and begin streaming the video.
The problem I am having is that once the client renders the video element and tries to find the source mp4 I get a 404 error not found on the video file. Any advice on this would be highly appreciated mates. :)
Controller function:
uploadVideo: function (req, res) {
var torrentStream = require('torrent-stream');
var mkdirp = require('mkdirp');
var rootPath = process.cwd();
var magnetLink = req.param('magnet_link');
var fs = require('fs');
var engine = torrentStream(magnetLink);
engine.on('ready', function() {
engine.files.forEach(function(file) {
var fileName = file.name;
var filePath = file.path;
console.log(fileName + ' - ' + filePath);
var stream = file.createReadStream();
mkdirp(rootPath + '/assets/videos/' + fileName, function (err) {
if (err) {
console.log(err);
} else {
var videoPath = rootPath + '/assets/videos/' + fileName + '/video.mp4';
var writer = fs.createWriteStream(videoPath);
var videoSent = false;
stream.on('data', function (data) {
writer.write(data);
if (!videoSent) {
fs.exists(videoPath, function(exists) {
if (exists) {
sails.sockets.broadcast(req.param('room'), 'video_ready', {videoPath: '/videos/' + fileName + '/video.mp4'});
videoSent = true;
}
});
}
});
// stream is readable stream to containing the file content
}
});
});
});
res.json({status: 'downloading'});
}
Client javascript:
$(document).ready(function () {
io.socket.on('connect', function () {
console.log('mrah');
io.socket.get('/join', {roomName: 'cowboybebop'});
io.socket.on('message', function (data) {
console.log(data);
});
io.socket.on('video_ready', function (data) {
var video = $('<video width="320" height="240" controls>\
<source src="' + data.videoPath + '" type="video/mp4">\
Your browser does not support the video tag.\
</video>');
$('body').append(video);
});
});
$('form').submit(function (e) {
e.preventDefault();
var formData = $(this).serialize();
$.ajax({
url: '/upload-torrent',
method: 'POST',
data: formData
}).success(function (data) {
console.log(data);
}).error(function (err) {
console.log(err);
});
});
});
Form:
<form action="/upload-torrent" method="POST">
<input name="magnet_link" type="text" value="magnet:?xt=urn:btih:565DB305A27FFB321FCC7B064AFD7BD73AEDDA2B&dn=bbb_sunflower_1080p_60fps_normal.mp4&tr=udp%3a%2f%2ftracker.openbittorrent.com%3a80%2fannounce&tr=udp%3a%2f%2ftracker.publicbt.com%3a80%2fannounce&ws=http%3a%2f%2fdistribution.bbb3d.renderfarming.net%2fvideo%2fmp4%2fbbb_sunflower_1080p_60fps_normal.mp4"/>
<input type="hidden" name="room" value="cowboybebop">
<input type="submit" value="Link Torrent">
You may be interested in Torrent Stream Server. It a server that downloads and streams video at the same time, so you can watch the video without fully downloading it. It's based on the same torrent-stream library which you are exploring and has functionally you are trying to implement, so it may be a good reference for you.
Also, I suggest taking a look at webtorrent. It's a nice torrent library that works in both: NodeJs & browser and has streaming support. It sounds great, but from my experience, it doesn't have very good support in the browser.

Resources