I looked at several SO posts trying to find a way to make a Node.js server tell a client to stop uploading after a certain file size has been reached. The most promising of these is ed-ta's technique at Avoiding further processing on busyboy file upload size limit.
Per ed-ta, my Node.js server seemed to be doing what it should. The server sent the 455 status code as soon as the size limit was reached and stopped accepting any more data. Unfortunately, my client kept processing the file until it was completely done anyway. This is a less than ideal experience when the user tries to upload extremely large files since the client doesn't alert the user that the threshold is reached until the AJAX request is completely done.
How do I get the client to see the 455 status code in a timely manner?
I tried checking for the 455 status inside xhr.onreadystatechange, but I can't seem to find that information from inside onreadystatehange even if the server has already sent the 455 in the response 1. Also, the onreadystatechange event doesn't seem to trigger until after the entire file has been processed by the client anyway.
I have tried to simplify the problem by getting rid of the irrelevant details and my current demo code follows:
Server.js
// This code is based on
// https://stackoverflow.com/questions/23691194/node-express-file-upload
//
// [1] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [2] - https://stackoverflow.com/questions/18310394/
// no-access-control-allow-origin-node-apache-port-issue
//
// [3] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [4] - https://stackoverflow.com/questions/44736327/
// node-js-cors-issue-response-to-preflight-
// request-doesnt-pass-access-control-c
var express = require('express');
var busboy = require('connect-busboy');
var fs = require('fs-extra');
var cors = require('cors'); // [4]
const app = express();
// See [2][4]
app.use(
function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "null");
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, POST")
next();
}
);
app.options('*', cors());
app.use(
busboy({ limits: { files: 1, fileSize: 500000000 } }) // [1]
);
app.post('/uploadEndpoint', function (req, res, next) {
var fStream;
req.pipe(req.busboy);
req.busboy.on('file', function (fieldName, file, filename) {
console.log("Uploading: " + filename);
var destPath = './' + filename;
fStream = fs.createWriteStream(destPath);
file.pipe(fStream);
// ed-ta [3]
// Despite being asynchronous limit_reach
// will be seen here as true if it hits max size
// as set in file.on.limit because once it hits
// max size the stream will stop and on.finish
// will be triggered.
var limit_reach = false;
req.busboy.on('finish', function() {
if(!limit_reach){
res.send(filename + " uploaded");
}
});
file.on('limit', function() {
fs.unlink(destPath, function(){
limit_reach = true;
res.status(455).send("File too big.");
console.log('Telling client to stop...');
});
});
});
})
app.listen(8000);
test.html
<!DOCTYPE html>
<!--
This code is based off timdream and Basar at
https://stackoverflow.com/questions/6211145/
upload-file-with-ajax-xmlhttprequest
[1] - https://stackoverflow.com/questions/49692745/
express-using-multer-error-multipart-boundary-not-found-request-sent-by-pos
-->
<html>
<head>
<meta charset="utf-8" />
<script>
function uploadFile() {
var progress = document.getElementById("output");
progress.innerText = "Starting";
var fileCtl = document.getElementById("theFile");
var file = fileCtl.files[0];
var xhr = new XMLHttpRequest();
// timdream
var formData = new FormData();
formData.append('theFile', file);
xhr.upload.onprogress = (progressEvent) => {
var percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
progress.innerText =
"Percent Uploaded: " + percentCompleted + "%";
};
xhr.onreadystatechange = function(e) {
if (this.status === 455) {
alert('Sorry, file was too big.');
}
};
xhr.open('post', 'http://localhost:8000/uploadEndpoint', true);
// Apparently this line causes issues Multipart Boundary not
// found error [1]
// xhr.setRequestHeader("Content-Type","multipart/form-data");
// timdream
xhr.send(formData);
}
</script>
</head>
<body>
<input type="file" id="theFile" name="theName" /><br />
<div id="output">Upload Progress</div>
<input type="button" id="theButton"
onclick="uploadFile();" value="Send" />
</body>
</html>
1 - I could setup another endpoint on the Node.js server and use AJAX to poll that endpoint for the current status inside the client's onprogress but that seems like a kludgy solution that would waste bandwidth.
To get around the problem described above, I wound up using a separate WebSocket channel to send a message from the server back down to the client to tell the said client to stop the upload. I then called abort on the client's XMLHttpRequest object per the Mozilla docs.
Final sample code looks like this:
Server.js
// This code is based on
// https://stackoverflow.com/questions/23691194/node-express-file-upload
//
// [1] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [2] - https://stackoverflow.com/questions/18310394/
// no-access-control-allow-origin-node-apache-port-issue
//
// [3] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [4] - https://stackoverflow.com/questions/44736327/
// node-js-cors-issue-response-to-preflight-
// request-doesnt-pass-access-control-c
var express = require('express');
var busboy = require('connect-busboy');
var fs = require('fs-extra');
var cors = require('cors'); // [4]
var ws = require('ws');
var WebSocketServer = ws.WebSocketServer;
var g_ws;
// BEGIN FROM: https://www.npmjs.com/package/ws
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', function connection(ws) {
g_ws = ws;
});
// END FROM: https://www.npmjs.com/package/ws
const app = express();
// See [2][4]
app.use(
function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "null");
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, POST")
next();
}
);
app.options('*', cors());
app.use(
busboy({ limits: { files: 1, fileSize: 300000000 } }) // [1]
);
app.post('/uploadEndpoint', function (req, res, next) {
var fStream;
req.pipe(req.busboy);
req.busboy.on('file', function (fieldName, file, fileNameObject) {
var filename = fileNameObject.filename;
console.log("Uploading: " + filename);
var destPath = './' + filename;
fStream = fs.createWriteStream(destPath);
file.pipe(fStream);
// ed-ta [3]
// Despite being asynchronous limit_reach
// will be seen here as true if it hits max size
// as set in file.on.limit because once it hits
// max size the stream will stop and on.finish
// will be triggered.
var limit_reach = false;
req.busboy.on('finish', function() {
var message;
if(!limit_reach){
message = 'success';
res.send(filename + " uploaded");
} else {
message = 'TooBig';
}
g_ws.send(message);
});
file.on('limit', function() {
fs.unlink(destPath, function(){
limit_reach = true;
res.status(455).send("File too big.");
console.log('Telling client to stop...');
// https://www.npmjs.com/package/ws
g_ws.send("TooBig");
});
});
});
})
app.listen(8000);
test.html
<!DOCTYPE html>
<!--
This code is based off timdream and Basar at
https://stackoverflow.com/questions/6211145/
upload-file-with-ajax-xmlhttprequest
[1] - https://stackoverflow.com/questions/49692745/
express-using-multer-error-multipart-boundary-not-found-request-sent-by-pos
-->
<html>
<head>
<meta charset="utf-8" />
<script>
function uploadFile() {
var progress = document.getElementById("output");
progress.innerText = "Starting";
var fileCtl = document.getElementById("theFile");
var file = fileCtl.files[0];
var xhr = new XMLHttpRequest();
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('message', function (event) {
if (event.data === 'TooBig') {
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/abort
xhr.abort();
alert('Server says file was too big.');
} else if (event.data === 'success') {
alert('File uploaded sucessfully.');
} else {
alert('Unknown server error');
}
socket.close();
});
// timdream
var formData = new FormData();
formData.append('theFile', file);
xhr.upload.onprogress = (progressEvent) => {
var percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
progress.innerText =
"Percent Uploaded: " + percentCompleted + "%";
};
xhr.onreadystatechange = function(e) {
if (this.status === 455) {
alert('Sorry, file was too big.');
}
};
xhr.open('post', 'http://localhost:8000/uploadEndpoint', true);
// Apparently this line causes issues Multipart Boundary not
// found error [1]
// xhr.setRequestHeader("Content-Type","multipart/form-data");
// timdream
xhr.send(formData);
}
</script>
</head>
<body>
<input type="file" id="theFile" name="theName" /><br />
<div id="output">Upload Progress</div>
<input type="button" id="theButton"
onclick="uploadFile();" value="Send" />
</body>
</html>
This is not the exact code I used for my solution but this simplified working demo illustrates the concept.
BTW: Filesize limit was lowered to 300000000 on the server to make testing easier but that doesn't matter.
Related
I write a program with native nodejs uploading PNG and storing it.Here is my code:
client side:
<html>
<head>
<title>a demo</title>
</head>
<body>
<input type="file" value="upload file">
<script>
const uploadButton = document.querySelector("input[type=file]");
uploadButton.addEventListener("change", uploadFile);
function uploadFile(event) {
const files = event.target.files;
const xhr = new XMLHttpRequest();
xhr.responseType = "json";
const formData = new FormData();
formData.append('file', files[0]);
xhr.open("POST", `http://localhost:8099/form`);
xhr.send(formData);
}
</script>
</body>
</html>
when I click the button, a dialog will ask me which file to upload, I choose a .png file.Here is the body of the request:
------WebKitFormBoundary3pjijTW4qZ1jJZFS
Content-Disposition: form-data; name="file"; filename="截屏2022-08-14 22.18.52.png"
Content-Type: image/png
------WebKitFormBoundary3pjijTW4qZ1jJZFS--
here is screenshot in Safari:
Here is my server-side code:
const http = require("http");
const fs = require("fs");
const server = http.createServer((req, res) => {
res.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT");
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Length, Content-Type");
if(req.method === "OPTIONS") {
res.end("ok");
return;
}
const regForm = /\/form/;
if(req.url.search(regForm) !== -1) {
// Get boundary from "Content-Type" of request headers
const [_, boundary] = /boundary=(.*)$/.exec(req.headers['content-type']) || [];
const data = [];
// read the body of request, and store it in data
req.on("data", (chunk) => {
data.push(chunk);
});
// when finish reading the body of request
req.on("end", () => {
// if content-type is multipart/form-data, then content format is:
// --{boundary}\r\n
// Content-Disposition: {value}\r\n
// Content-Type: {value}\r\n
// \r\n
// {content}\r\n
// --{boundary}--
const joinedData = Buffer.from(data.join(""));
const formData = data.join("").split('\r\n');
// parse joinedData, make sure the position of .png content
const content_start = joinedData.indexOf('\r\n\r\n') + 4;
const content_end = joinedData.lastIndexOf(`\r\n--${boundary}--`);
const ContentDispositionString = formData[1];
const ContentTypeString = formData[2];
const content = joinedData.subarray(content_start, content_end);
// Get fileName
const [__, fileName] = /filename="(.*)"$/.exec(ContentDispositionString) || [];
if (fileName) {
// yes, I have a directory called path
const writer = fs.createWriteStream(`./path/${fileName}`, 'binary');
// is there any problem here?
writer.write(content);
writer.end();
}
res.end("ok");
});
return;
}
});
server.listen(8099, () => {
console.log("server is listening");
});
After deal with the request, the program creates a .png file in path directory, but I
cannot open it, vscode tells me that An error occurred while loading the image.
I have spent 5 hours searching the ways to solve the problem, unfortunately
nothing helps.
Any info or comment will be greatly appreciated!
I am trying to create a two-way communication voice bot using detectIntentStream Api of Dialogflow ES. But unable to stop streaming or end the session after communication.
I use example2 of this repository: https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/examples
here is the server code
const projectId = process.env.npm_config_PROJECT_ID;
const example = process.env.npm_config_EXAMPLE;
const port = process.env.npm_config_PORT || 3000;
const languageCode = "en-US";
let encoding = "AUDIO_ENCODING_LINEAR_16";
if (example > 3) {
// NOTE: ENCODING NAMING FOR SPEECH API IS DIFFERENT
encoding = "LINEAR16";
}
console.log(example);
if (example == 7) {
// NOTE: ENCODING NAMING FOR SPEECH API IS DIFFERENT
encoding = "linear16";
}
const singleUtterance = false;
const interimResults = false;
const sampleRateHertz = 16000;
const speechContexts = [
{
phrases: ["mail", "email"],
boost: 20.0,
},
];
console.log(example);
console.log(projectId);
// ----------------------
// load all the libraries for the server
const socketIo = require("socket.io");
const path = require("path");
const fs = require("fs");
const http = require("http");
const cors = require("cors");
const express = require("express");
const ss = require("socket.io-stream");
// load all the libraries for the Dialogflow part
const uuid = require("uuid");
const util = require("util");
const { Transform, pipeline } = require("stream");
const pump = util.promisify(pipeline);
const df = require("dialogflow").v2beta1;
// set some server variables
const app = express();
var server;
var sessionId, sessionClient, sessionPath, request;
var speechClient,
requestSTT,
ttsClient,
requestTTS,
mediaTranslationClient,
requestMedia;
// STT demo
const speech = require("#google-cloud/speech");
// TTS demo
const textToSpeech = require("#google-cloud/text-to-speech");
// Media Translation Demo
const mediatranslation = require("#google-cloud/media-translation");
/**
* Setup Express Server with CORS and SocketIO
*/
function setupServer() {
// setup Express
app.use(cors());
app.get("/", function (req, res) {
res.sendFile(path.join(__dirname + "/example" + example + ".html"));
});
server = http.createServer(app);
io = socketIo(server);
server.listen(port, () => {
console.log("Running server on port %s", port);
});
// Listener, once the client connect to the server socket
io.on("connect", (client) => {
console.log(`Client connected [id=${client.id}]`);
client.emit("server_setup", `Server connected [id=${client.id}]`);
ss(client).on("stream", function (stream, data) {
// get the name of the stream
const filename = path.basename(data.name);
// pipe the filename to the stream
stream.pipe(fs.createWriteStream(filename));
// make a detectIntStream call
detectIntentStream(stream, function (results) {
console.log(results);
console.log(results.outputAudio);
client.emit("results", results);
});
});
});
}
function setupDialogflow() {
// Dialogflow will need a session Id
sessionId = uuid.v4();
// Dialogflow will need a DF Session Client
// So each DF session is unique
sessionClient = new df.SessionsClient();
// Create a session path from the Session client,
// which is a combination of the projectId and sessionId.
sessionPath = sessionClient.sessionPath(projectId, sessionId);
// Create the initial request object
// When streaming, this is the first call you will
// make, a request without the audio stream
// which prepares Dialogflow in receiving audio
// with a certain sampleRateHerz, encoding and languageCode
// this needs to be in line with the audio settings
// that are set in the client
request = {
session: sessionPath,
queryInput: {
audioConfig: {
sampleRateHertz: sampleRateHertz,
encoding: encoding,
languageCode: languageCode,
speechContexts: speechContexts,
},
singleUtterance: singleUtterance,
},
outputAudioConfig: {
audioEncoding: "OUTPUT_AUDIO_ENCODING_LINEAR_16",
},
};
}
async function detectIntentStream(audio, cb) {
// execute the Dialogflow Call: streamingDetectIntent()
const stream = sessionClient
.streamingDetectIntent()
.on("data", function (data) {
// when data comes in
// log the intermediate transcripts
if (data.recognitionResult) {
console.log(
`Intermediate transcript:
${data.recognitionResult.transcript}`
);
} else {
// log the detected intent
console.log(`Detected intent:`);
cb(data);
}
})
.on("error", (e) => {
console.log(e);
})
.on("end", () => {
console.log("on end");
return null;
});
// Write request objects.
// Thee first message must contain StreamingDetectIntentRequest.session,
// [StreamingDetectIntentRequest.query_input] plus optionally
// [StreamingDetectIntentRequest.query_params]. If the client wants
// to receive an audio response, it should also contain
// StreamingDetectIntentRequest.output_audio_config.
// The message must not contain StreamingDetectIntentRequest.input_audio.
stream.write(request);
// pump is a small node module that pipes streams together and
// destroys all of them if one of them closes.
await pump(
audio,
// Format the audio stream into the request format.
new Transform({
objectMode: true,
transform: (obj, _, next) => {
next(null, {
inputAudio: obj,
outputAudioConfig: {
audioEncoding: `OUTPUT_AUDIO_ENCODING_LINEAR_16`,
},
});
},
}),
stream
);
}
setupDialogflow();
setupServer();
and here is client code
<!DOCTYPE html>
<!--
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<meta charset="utf-8" />
<title>RecordRTC over Socket.io</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css">
<script src="https://www.WebRTC-Experiment.com/RecordRTC.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io-stream/0.9.1/socket.io-stream.js"></script>
</head>
<body>
<div style="margin: 20px">
<h1 style="font-size: 18px;">Example 2: Dialogflow Speech Detection through streaming</h1>
<div>
<button id="start-recording" disabled>Start Streaming</button>
<button id="stop-recording" disabled>Stop Streaming</button>
</div>
<h2 style="font-size: 16px; margin-bottom: 10px;">Query Text</h2>
<code>data.queryResult.queryText</code><br/>
<input id="queryText" type="text" style="width: 400px;"/>
<h2 style="font-size: 16px; margin-bottom: 10px;">Intent</h2>
<code>data.queryResult.intent.displayName</code><br/>
<input id="intent" type="text" style="width: 400px;"/>
<h2 style="font-size: 16px;">Responses</h2>
<code>data.queryResult.fulfillmentText</code><br/>
<textarea id="results" style="width: 800px; height: 300px;"></textarea>
<code>data.queryResult.audio</code><br/>
<textarea id="result_audio" style="width: 800px; height: 300px;"></textarea>
</div>
<script type="text/javascript">
const startRecording = document.getElementById('start-recording');
const stopRecording = document.getElementById('stop-recording');
let recordAudio;
const socketio = io();
const socket = socketio.on('connect', function() {
startRecording.disabled = false;
});
startRecording.onclick = function() {
startRecording.disabled = true;
navigator.getUserMedia({
audio: true
}, function(stream) {
recordAudio = RecordRTC(stream, {
type: 'audio',
mimeType: 'audio/webm',
sampleRate: 44100,
desiredSampRate: 16000,
recorderType: StereoAudioRecorder,
numberOfAudioChannels: 1,
//1)
// get intervals based blobs
// value in milliseconds
// as you might not want to make detect calls every seconds
timeSlice: 4000,
//2)
// as soon as the stream is available
ondataavailable: function(blob) {
// 3
// making use of socket.io-stream for bi-directional
// streaming, create a stream
var stream = ss.createStream();
// stream directly to server
// it will be temp. stored locally
ss(socket).emit('stream', stream, {
name: 'stream.wav',
size: blob.size
});
// pipe the audio blob to the read stream
ss.createBlobReadStream(blob).pipe(stream);
}
});
recordAudio.stopRecording();
stopRecording.disabled = false;
}, function(error) {
console.error(JSON.stringify(error));
});
};
// 4)
// on stop button handler
stopRecording.onclick = function() {
// recording stopped
recordAudio.startRecording();
startRecording.disabled = false;
stopRecording.disabled = true;
// socketio.emit("close",function(){
// })
};
// const aduiopreview = document.getElementById('result_audio');
const resultpreview = document.getElementById('results');
const intentInput = document.getElementById('intent');
const textInput = document.getElementById('queryText');
socketio.on('results', function (data) {
console.log(data);
if(data.queryResult){
resultpreview.innerHTML += "" + data.queryResult.fulfillmentText;
intentInput.value = data.queryResult.intent.displayName;
textInput.value = "" + data.queryResult.queryText;
}
if(data.outputAudio){
console.log(data.outputAudio);
playOutput(data.outputAudio)
}
});
/*
* When working with Dialogflow and Dialogflow matched an intent,
* and returned an audio buffer. Play this output.
*/
function playOutput(arrayBuffer){
let audioContext = new AudioContext();
let outputSource;
try {
if(arrayBuffer.byteLength > 0){
console.log(arrayBuffer.byteLength);
audioContext.decodeAudioData(arrayBuffer,
function(buffer){
audioContext.resume();
outputSource = audioContext.createBufferSource();
outputSource.connect(audioContext.destination);
outputSource.buffer = buffer;
outputSource.start(0);
},
function(){
console.log(arguments);
});
}
} catch(e) {
console.log(e);
}
}
</script>
</body>
</html>
i need to send a PDF file from angularjs client to NodeJS service.
I did the angularjs service, and when i receive the file its a string like this:
%PDF-1.3
3 0 obj
<</Type /Page
/Parent 1 0 R
/Reso
How can i reconvert this string to PDF in NodeJS?
This is the client code:
var sendByEmail = function () {
$scope.generatingPdf = true;
$('#budget').show();
var pdf = new JsPDF('p', 'pt', 'letter');
var source = $('#budget')[0];
pdf.addHTML(source, 0, 0, function () {
var resultPdf = pdf.output();
BillService.sendByEmail("rbrlnx#gmail.com", resultPdf).then(function () {
});
$('#budget').hide();
});
};
var sendByEmail = function (email, file) {
var deferred = $q.defer();
var data = {
email: email,
file: file
};
BillService.sendByEmail(data, function (result) {
deferred.resolve(result);
}, function () {
deferred.reject();
});
return deferred.promise;
};
The server code controller its empty:
var sendByEmail = function (req, res, next) {
var file = req.body.file;
};
I experimented with this a while ago, and I came up with this. It's not production ready by a long shot maybe you find it useful. It's free of front end libraries (except Angular ofcourse), but assumes you're using Express 4x and body-parser.
The result:
In the browser:
On the server:
What you're seeing:
You're seeing a tiny node server, serving static index.html and angular files, and a POST route receiving a PDF in base64 as delivered by the HTML FileReader API, and saves it to disk.
Instead of saving to disk, you can send it as an email attachment. See for instance here or here for some info on that.
The example below assumes uploading a PDF by a user through a file input, but the idea is the same for all other ways of sending a document to your back end system. The most important thing is to send the pdf data as BASE64, because this is the format that most file writers and email packages use (as opposed to straight up binary for instance..). This also goes for images, documents etc.
How did I do that:
In your HTML:
<div pdfs>Your browser doesn't support File API.</div>
A directive called pdfs:
myApp.directive('pdfs', ['upload', function(upload) {
return {
replace: true,
scope: function() {
files = null;
},
template: '<input id="files" type="file">',
link: function(scope,element) {
element.bind('change', function(evt) {
scope.$apply(function() {
scope.files = evt.target.files;
});
});
},
controller: function($scope, $attrs) {
$scope.$watch('files', function(files) {
//upload.put(files)
if(typeof files !== 'undefined' && files.length > 0) {
for(var i = 0; i<files.length;i++) {
readFile(files[i])
}
}
}, true);
function readFile(file) {
var reader = new FileReader();
reader.addEventListener("loadend", function(evt) {
upload.post({name: file.name, data: reader.result})
})
if(reader.type = 'application/pdf') {
reader.readAsDataURL(file);
}
}
}
}
}]);
A tiny service:
myApp.service('upload', function($http) {
this.post = function(file) {
$http.post('/pdf', file);
}
});
And a node server:
var express = require('express');
var bodyParser = require('body-parser')
var fs = require("fs");
var app = express();
app.use(express.static('.'));
app.use( bodyParser.json({limit: '1mb'}) );
app.post('/pdf', function(req, res){
var name = req.body.name;
var pdf = req.body.data;
var pdf = pdf.replace('data:application/pdf;base64,', '');
res.send('received');
fs.writeFile(name, pdf, 'base64', function(err) {
console.log(err);
});
});
var server = app.listen(3000, function() {
console.log('Listening on port %d', server.address().port);
});
I'm trying to upload a image from a AngularJS interface to a nodejs server (expressjs).
(I'm using mean.io)
Every time I upload someting, req.body logs "{}" and req.files logs "undefined"
I'm using angular-file-upload directive in AngularJS
Client-side code:
$scope.onFileSelect = function() {
console.log($files);
for (var i = 0; i < $files.length; i++) {
var file = $files[i];
$scope.upload = $upload.upload({
url: 'map/set',
method: 'POST',
headers: {'enctype': 'multipart/form-data'},
data: {myObj: $scope.myModelObj},
file: file,
}).progress(function(evt) {
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
}).success(function(data, status, headers, config) {
// file is uploaded successfully
console.log(data);
});
}
};
Server-side code
var app = express();
require(appPath + '/server/config/express')(app, passport, db);
app.use(bodyParser({uploadDir:'./uploads'}));
app.post('/map/set', function(req, res) {
console.log(req.body);
console.log(req.files);
res.end('Success');
});
*****Edit*****
HTML Code
<div class="row">
<input id="file" type="file" ng-file-select="onFileSelect()" >
</div>
Hand built request
$scope.onFileSelect = function() {
//$files: an array of files selected, each file has name, size, and type.
//console.log($files);
var xhr = new XMLHttpRequest();
// not yet supported in most browsers, some examples use
// this but it's not safe.
// var fd = document.getElementById('upload').getFormData();
var fd = new FormData();
var files = document.getElementById('myfileinput').files;
console.log(files);
for(var i = 0;i<files.length; i++) {
fd.append("file", files[i]);
}
/* event listeners */
xhr.upload.addEventListener("progress", uploadProgress, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("load", uploadComplete, false);
xhr.addEventListener("abort", uploadCanceled, false);
function uploadComplete(){
console.log("complete");
}
function uploadProgress(){
console.log("progress");
}
function uploadFailed(){
console.log("failed");
}
function uploadCanceled(){
console.log("canceled");
}
xhr.open("POST", "map/set");
xhr.send(fd);
};
The latest version of mean.io uncluding express 4.x as dependency. In the documentation for migration express 3 to 4 you can read, express will no longer user the connect middlewares. Read more about here: https://github.com/visionmedia/express/wiki/Migrating-from-3.x-to-4.x
The new body-parser module only handles urlencoded and json bodies. That means for multipart bodies (file uploads) you need an additional module like busboy or formadible.
Here is an example how I use angular-file-upload with busboy:
The AngularJS Stuff:
$upload.upload({
url: '/api/office/imageUpload',
data: {},
file: $scope.$files
}) …
I write a little helper module to handle uploads with busboy easier. It’s not very clean coded, but do the work:
var env = process.env.NODE_ENV || 'development';
var Busboy = require('busboy'),
os = require('os'),
path = require('path'),
config = require('../config/config')[env],
fs = require('fs');
// TODO: implement file size limit
exports.processFileUpload = function(req, allowedExtensions, callback){
var busboy = new Busboy({ headers: req.headers });
var tempFile = '';
var fileExtenstion = '';
var formPayload = {};
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
fileExtenstion = path.extname(filename).toLowerCase();
tempFile = path.join(os.tmpDir(), path.basename(fieldname)+fileExtenstion);
file.pipe(fs.createWriteStream(tempFile));
});
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) {
var jsonValue = '';
try {
jsonValue = JSON.parse(val);
} catch (e) {
jsonValue = val;
}
formPayload[fieldname] = jsonValue;
});
busboy.on('finish', function() {
if(allowedExtensions.length > 0){
if(allowedExtensions.indexOf(fileExtenstion) == -1) {
callback({message: 'extension_not_allowed'}, tempFile, formPayload);
} else {
callback(null, tempFile, formPayload)
}
} else {
callback(null, tempFile, formPayload)
}
});
return req.pipe(busboy);
}
In my controller i can use the module that way:
var uploader = require('../helper/uploader'),
path = require('path');
exports.uploadEmployeeImage = function(req,res){
uploader.processFileUpload(req, ['.jpg', '.jpeg', '.png'], function(uploadError, tempPath, formPayload){
var fileExtenstion = path.extname(tempPath).toLowerCase();
var targetPath = "/exampleUploadDir/testFile" + fileExtenstion;
fs.rename(tempPath, targetPath, function(error) {
if(error){
return callback("cant upload employee image");
}
callback(null, newFileName);
});
});
}
I'm going to take a guess here that the header settings are incorrect.
headers: {'enctype': 'multipart/form-data'},
Should be changed to:
headers: {'Content-Type': 'multipart/form-data'},
Ensure you have an 'id' AND 'name' attribute on the file input - not having an id attribute can cause problems on some browsers. Also, try building the request like this:
var xhr = new XMLHttpRequest();
// not yet supported in most browsers, some examples use
// this but it's not safe.
// var fd = document.getElementById('upload').getFormData();
var fd = new FormData();
var files = document.getElementById('myfileinput').files;
for(var i = 0;i<files.length; i++) {
fd.append("file", files[i]);
}
/* event listeners */
xhr.upload.addEventListener("progress", uploadProgress, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("load", uploadComplete, false);
xhr.addEventListener("abort", uploadCanceled, false);
xhr.open("POST", "your/url");
xhr.send(fd);
angular isn't great with file uploads so doing it by hand might help.
I am trying to add a new URL path (Node.js home page has given an example to do it.) to a piece of complex existing code (shown below). The existing code already has server configuration code but the code looks very different from the "Building a Node.js Web Server" example that is showing in the Node.js home page.
The new URL path is "/lens/v1/ping" that is sent from the browser window.
If that is the URL path received, I am supposed to send a response Status 200 back to the browser.
I am having difficulties to fit this new URL path and its associated code to the existing code (show below) and I am seeking help. Thank you very much.
/**
* Entry point for the RESTFul Service.
*/
var express = require('express')
var config = require('config');
var _ = require('underscore');
var bodyParser = require("vcommons").bodyParser;
// Export config, so that it can be used anywhere
module.exports.config = config;
var Log = require('vcommons').log;
var logger = Log.getLogger('SUBSCRIBE', config.log);
var http = require("http");
var https = require("https");
var fs = require("fs");
var cluster = require("cluster");
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('online', function (worker) {
logger.info('A worker with #' + worker.id);
});
cluster.on('listening', function (worker, address) {
logger.info('A worker is now connected to ' + address.address + ':' + address.port);
});
cluster.on('exit', function (worker, code, signal) {
logger.info('worker ' + worker.process.pid + ' died');
});
} else {
logger.info("Starting Subscription Application");
createApp();
}
// Create Express App
function createApp() {
var app = express();
app.configure(function () {
// enable web server logging; pipe those log messages through winston
var winstonStream = {
write : function (message, encoding) {
logger.trace(message);
}
}; // Log
app.use(bodyParser({}));
app.use(app.router);
if (config.debug) {
app.use(express.errorHandler({
showStack : true,
dumpExceptions : true
}));
}
});
// Include Router
var router = require('../lib/router')();
// Subscribe to changes by certain domain for a person
app.post('/lens/v1/:assigningAuthority/:identifier/*', router.submitRequest);
// Listen
if (!_.isUndefined(config.server) || !_.isUndefined(config.secureServer)) {
if (!_.isUndefined(config.server)) {
http.createServer(app).listen(config.server.port, config.server.host, function () {
logger.info("Subscribe server listening at http://" + config.server.host + ":"
+ config.server.port);
});
}
if (!_.isUndefined(config.secureServer)) {
https.createServer(fixOptions(config.secureServer.options), app).listen
(config.secureServer.port, config.secureServer.host, function () {
logger.info("Subscribe server listening at https://" + config.secureServer.host
+ ":" + config.secureServer.port);
});
}
} else {
logger.error("Configuration must contain a server or secureServer.");
process.exit();
}
}
function fixOptions(configOptions) {
var options = {};
if (!_.isUndefined(configOptions.key) && _.isString(configOptions.key)) {
options.key = fs.readFileSync(configOptions.key);
}
if (!_.isUndefined(configOptions.cert) && _.isString(configOptions.cert)) {
options.cert = fs.readFileSync(configOptions.cert);
}
if (!_.isUndefined(configOptions.pfx) && _.isString(configOptions.pfx)) {
options.pfx = fs.readFileSync(configOptions.pfx);
}
return options;
}
// Default exception handler
process.on('uncaughtException', function (err) {
logger.error('Caught exception: ' + err);
process.exit()
});
// Ctrl-C Shutdown
process.on('SIGINT', function () {
logger.info("Shutting down from SIGINT (Crtl-C)")
process.exit()
})
// Default exception handler
process.on('exit', function (err) {
logger.info('Exiting.. Error:', err);
});
A browser window executes a HTTP GET request. After the // Include Router call, that is where you would define your express GET function.
...
// Include Router
var router = require('../lib/router')();
//*********
//define your method here, as shown, or in your router file.
app.get('lens/v1/ping', function(req, res){
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end();
} );
//*********
// Subscribe to changes by certain domain for a person
app.post('/lens/v1/:assigningAuthority/:identifier/*', router.submitRequest);
...