import Vue from 'vue'
import App from './App.vue'
// import both the QEWD class and VueQEWD plugin
import { QEWD, VueQEWD } from 'vue-qewd'
// instantiate QEWD with your parameters
var qewd = QEWD({
application: 'vue-test', // <========
log: true,
url: 'http://192.168.1.135:8080' // <=========
})
// let Vue know you want to use the plugin
Vue.use(VueQEWD, { qewd })
// create your Vue instance
new Vue({
el: '#app',
render: h => h(App)
})
<template>
<div id="app">
<template v-if="qewdReady">
<img src="./assets/logo.png">
<h1>{{ msg }}</h1>
<button @click="testing">QEWD message test</button>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>File
<input type="file" id="singlefile" ref="file" v-on:change="handleFileUpload()"/>
</label>
<button v-on:click="submitFile()">Submit</button>
</div>
</div>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>Files
<input type="file" id="multifile" ref="files" multiple v-on:change="handleFilesUpload()"/>
</label>
<button v-on:click="submitFiles()">Submit</button>
</div>
</div>
</template>
<template v-else>
<img src="./assets/logo.png">
<h2>Starting application, please wait ...</h2>
</template>
</div>
</template>
<script>
import axios from 'axios'
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2) return parts.pop().split(";").shift();
return '';
}
export default {
name: 'app',
created: function () {
var self = this
// monitor when QEWD is ready
this.$qewd.vRegistrationCallback(function (registered) {
// save QEWD Session token as cookie
self.$qewd.setCookie('qewd');
self.qewdReady = registered //
})
// start the QEWD WebSockets connection ...
this.$qewd.vstart()
},
data () {
return {
qewdReady: false,
msg: 'Welcome to Your Vue.js App',
file: '',
files: ''
}
},
methods: {
testing: function () {
let messageObj = {
type: 'test',
params: {
text: 'a Vue.js test message for QEWD'
}
};
let self = this;
this.$qewd.send(messageObj, function(messageObj) {
self.msg = messageObj.message.text;
});
},
/*
Submits the file to the server
*/
submitFile(){
/*
Initialize the form data
*/
console.log('submitfile!');
let formData = new FormData();
/*
Add the form data we need to submit
*/
formData.append('singlefile', this.file);
/*
Make the request to the POST /single-file URL
*/
console.log('axios post');
axios.post( '/upload',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
Authorization: 'Bearer ' + getCookie('qewd')
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
},
/*
Handles a change on the file upload
*/
handleFileUpload(){
this.file = this.$refs.file.files[0];
},
/*
Submits multiple files to the server
*/
submitFiles(){
/*
Initialize the form data
*/
let formData = new FormData();
/*
Iteate over any file sent over appending the files
to the form data.
*/
for( var i = 0; i < this.files.length; i++ ){
let file = this.files[i];
formData.append('multifile', file);
}
/*
Make the request to the POST /multiple-files URL
*/
axios.post( '/multiple-files',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
Authorization: 'Bearer ' + getCookie('qewd')
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
},
/*
Handles a change on the file upload
*/
handleFilesUpload(){
this.files = this.$refs.files.files;
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
Let me point out the key bits in this file to take note of.
First, when the application registers itself against the QEWD server, it saves the QEWD Session Token as a cookie:
this.$qewd.vRegistrationCallback(function (registered) {
// save QEWD Session token as cookie
self.$qewd.setCookie('qewd'); // <=============
self.qewdReady = registered
})
You'll see why we did this later.
Next, the input fields and associated buttons for uploading a single file:
<label>File
<input type="file" id="singlefile" ref="file" v-on:change="handleFileUpload()"/>
</label>
<button v-on:click="submitFile()">Submit</button>
and multiple files:
<label>Files
<input type="file" id="multifile" ref="files" multiple v-on:change="handleFilesUpload()"/>
</label>
<button v-on:click="submitFiles()">Submit</button>
The buttons invoke the functions submitFile() and submitFiles() respectively, which, in turn, invoke the axios method for uploading the files, eg:
submitFile(){
/*
Initialize the form data
*/
console.log('submitfile!');
let formData = new FormData();
/*
Add the form data we need to submit
*/
formData.append('singlefile', this.file);
/*
Make the request to the POST /single-file URL
*/
console.log('axios post');
axios.post( '/upload',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
Authorization: 'Bearer ' + getCookie('qewd')
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
},
Notice that I'm sending the QEWD Session Token cookie with the request, in the Authorization header:
Authorization: 'Bearer ' + getCookie('qewd')
This allows me to verify the authenticity of the user / browser who is trying to upload files, and, if necessary, prevent them from doing so.
That's basically it for the front-end - pretty straightforward stuff.
So now let's look at the QEWD back-end.
By default, QEWD.js uses the standard Node.js Express web server, and that's what I'll use here too. One thing that Express doesn't provide for you is APIs for file uploading, but one of the most common modules you can use to provide such APIs is "multer" ( https://www.npmjs.com/package/multer )
Multer is an Express middleware, which means that we much add it within our QEWD's master process, which is where all its activities take place. This has a couple of consequences:
- when developing and debugging your Multer upload logic, you'll need to stop and restart the master process to get any changes to take effect - stopping the child / worker processes won't do any good!
- when it comes to validating the Authorization header that contains the QEWD Session Token, the master process doesn't have the information needed. We'll have to engineer a way of passing it to a worker process and perform the validation there: the worker processes have access to the QEWD Session storage. The Master process doesn't.
We need to add the Multer file upload logic into our QEWD startup file. Here's my simple example:
var config = {
managementPassword: 'keepThisSecret!',
serverName: 'QEWD Server',
port: 8080,
poolSize: 2,
database: {
type: 'gtm'
}
};
config.addMiddleware = function(bodyParser, app, q) {
var multer = require('multer');
var fs = require('fs');
var dest_path = '/home/rtweed/qewd';
var upload = multer({
dest: dest_path
});
function authenticate(req, res, next) {
console.log('*** authenticate *** ');
// first we need to make sure the request is authorised
// via the authorization header which should contain the QEWD session token
var error;
var authorization = req.headers.authorization;
console.log('** checking authorization: ' + authorization);
if (!authorization || authorization === '') {
error = {error: 'Empty or missing authorization header'};
res.status(400);
res.send(error);
return;
}
if (authorization.indexOf('Bearer ') === -1) {
error = {error: 'Invalid authorization header'};
res.status(400);
res.send(error);
return;
}
var token = authorization.split('Bearer ')[1];
if (token === '') {
error = {error: 'Empty bearer token'};
res.status(400);
res.send(error);
return;
}
// A token was found in the authorization header
// send a message to vue-test worker process application handler to check validity of token
var messageObj = {
type: 'authenticate',
application: 'vue-test',
token: token
};
q.handleMessage(messageObj, function(response) {
if (response.message.error) {
res.status(400);
res.send(response.message.error);
return;
}
// token was valid so Ok to upload file
console.log('** authorised ok!');
next();
});
}
function uploadFile(req, res, next) {
// token was valid so Ok to upload file
var uploadSingle = upload.single('singlefile');
uploadSingle(req, res, function (err) {
if (err) {
// something went wrong with the upload
res.status(400);
res.send(response.message.error);
return;
}
// rename the temporary uploaded file using the original file name
var target_path = dest_path + '/' + req.file.originalname;
fs.rename(req.file.path, target_path);
res.send('Successfully uploaded!');
});
}
function uploadFiles(req, res, next) {
console.log('** uploadFiles **');
var uploadArray = upload.array('multifile', 5);
uploadArray(req, res, function (err) {
if (err) {
// something went wrong with the upload
res.status(400);
res.send(response.message.error);
return;
}
console.log('*** files uploaded: ' + JSON.stringify(req.files, null, 2));
req.files.forEach(function(file) {
// rename each temporary file
var target_path = dest_path + '/' + file.originalname;
fs.rename(file.path, target_path);
});
res.send('Successfully uploaded!');
});
}
app.post('/upload', [authenticate, uploadFile]);
app.post('/multiple-files', [authenticate, uploadFiles]);
};
var qewd = require('qewd').master;
qewd.start(config);
This looks a bit daunting, but it's pretty straightforward really. Let's go through the key bits:
I've added the Multer code to the QEWD Master process by using the configuration object's addMiddleWare() function:
config.addMiddleware = function(bodyParser, app, q) {...}
Note that third argument which gives us access to the ewd-qoper8 / QEWD APIs. We'll be needing that!
Inside this function we initialise Multer:
var multer = require('multer');
var dest_path = '/home/rtweed/qewd';
var upload = multer({
dest: dest_path
});
Change the dest_path to the one where you want any files uploaded
Jump towards the end of the startup file and you'll see where I define the routes for handling single and multiple file uploads:
app.post('/upload', [authenticate, uploadFile]);
app.post('/multiple-files', [authenticate, uploadFiles]);
These match the axios code in the front-end, eg:
axios.post( '/upload',
axios.post( '/multiple-files',
You'll notice that both routes perform a chain of two middleware functions, the first of which authenticates the request. The trick is that the second one, which does the actual file upload, will only get invoked if the authentication was OK/
Let's see how this works.
The authenticate() function extracts the QEWD Session token from the Authorization header. If the header doesn't exist, or the token is invalid for whatever reason, an error (Status code 400) is returned to the browser, and the processing is aborted - any files selected by the user will be ignored!
The next step is to validate the QEWD Session token. This has to be done in a Worker process, but we're currently in the QEWD Master process. So what we can do is manually send a message to a Worker. The trick is to create a message that will access the same QEWD application handler module as normal web-socket messages sent from the browser. This is what this code is doing:
var messageObj = {
type: 'authenticate',
application: 'vue-test',
token: token
};
q.handleMessage(messageObj, function(response) { ... }
This will send a message to an available Worker process and invoke the authenticate() message handler function for the "vue-test" application.
Remember right at the start of this posting I noted that the application was registered as an application named "vue-test"
By constructing the message in this way, we're doing exactly what QEWD would do with a standard WebSocket message.
If the token is for a valid QEWD Session that hasn't timed out, then the authenticate() handler function will be invoked - we'll look at that in a minute.
However, if the token is invalid, QEWD will automatically return an error to the q.handleMessage() callback function. So that's what we check for:
if (response.message.error) {
res.status(400);
res.send(response.message.error);
return;
}
So we'll send an error message back to the browser and stop the Middleware chain in its tracks (using that return;)
But if the token was OK, we'll allow the Middleware chain to continue, by using the next() function:
// token was valid so Ok to upload file
console.log('** authorised ok!');
next();
So now we can perform the upload using the appropriate Multer API.
For single files we use upload.single()
For multiple files we use upload.array()
Now for a REALLY IMPORTANT bit, which can seriously catch you out.
Look at these two lines which set up the single() and array() methods:
var uploadSingle = upload.single('singlefile');
var uploadArray = upload.array('multifile', 5);
The first argument of each MUST be the same as the id you used in the browser's <input type=file> tag.
So go back and look at the front end code:
<input type="file" id="singlefile" ref="file" v-on:change="handleFileUpload()"/>
<input type="file" id="multifile" ref="files" multiple v-on:change="handleFilesUpload()"/>
Furthermore, it must be the same as the name you used in your axios functions in the front-end logic:
formData.append('singlefile', this.file); // <=========
for( var i = 0; i < this.files.length; i++ ){
let file = this.files[i];
formData.append('multifile', file); // <============
}
If you have a mismatch, you'll get an almost completely meaningless error that will crash the Master process - if you see such an error, check you've matched up those id values across your code!
Finally, let's take a look at the back-end application handler module:
module.exports = {
handlers: {
test: function(messageObj, session, send, finished) {
var incomingText = messageObj.params.text;
var d = new Date();
finished({text: 'You sent: ' + incomingText + ' at ' + d.toUTCString()});
},
authenticate: function(messageObj, session, send, finished) {
// if the token was invalid, QEWD will automatically have returned an error before getting here
// so this just needs to return a valid message if ok
// if the application includes a login step, you'd check here to confirm that the user
// has logged in - if not, return an error
// Check this by inspecting session.authenticated to see if it's true or false
finished({ok: true});
}
}
};
Note the comments about how to cater for an additional login step - typically when logging a user, your login() handler function will have set session.authenticated = true after a successful login.
So that's pretty much it - hopefully the rest of the code is self-explanatory. All being well, when you click those file upload buttons in your browser, you'll see the files magically appear on the QEWD server in the directory path you specify, and with the same name as the original.
POST http://localhost:8081/upload 404 (Not Found)
I'm well excited about this :)... haven't been able to get it to work in my project though.The error I'm getting is:
http://localhost:8081/upload
I wonder if this is because I'm using routes, not just config? E.g.:
var master = require('qewd').master;
var qewd = master.intercept();
master.start(config, routes);
--
You received this message because you are subscribed to the Google Groups "Enterprise Web Developer Community" group.
To unsubscribe from this group and stop receiving emails from it, send an email to enterprise-web-develope...@googlegroups.com.
To post to this group, send email to enterprise-web-de...@googlegroups.com.
Visit this group at https://groups.google.com/group/enterprise-web-developer-community.
For more options, visit https://groups.google.com/d/optout.
var cors = require('cors');
var corsOptions = {
origin: 'http://localhost:8081',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
};
app.post('/upload', cors(corsOptions), [authenticate, uploadFile]);
app.post('/multiple-files', cors(corsOptions), [authenticate, uploadFiles]);
function onStarted() {
var self = this;
setTimeout(function() {
// fire up your logic here
},2000);
}
module.exports = {
config: config,
routes: local_routes,
onStarted: onStarted
};
config.addMiddleware = function(bodyParser, app, q) {...})
--
You received this message because you are subscribed to the Google Groups "Enterprise Web Developer Community" group.
To unsubscribe from this group and stop receiving emails from it, send an email to enterprise-web-develope...@googlegroups.com.
To post to this group, send email to enterprise-web-de...@googlegroups.com.
Visit this group at https://groups.google.com/group/enterprise-web-developer-community.
For more options, visit https://groups.google.com/d/optout.
** authorised ok!
** uploadFiles **
*** files uploaded: [
{
"fieldname": "multifile",
"originalname": "4k-wallpapers-24.jpg",
"encoding": "7bit",
"mimetype": "image/jpeg",
"destination": "upload/",
"filename": "df6d0e08ed85fa5bdc3030d30ff5cd81",
"path": "upload/df6d0e08ed85fa5bdc3030d30ff5cd81",
"size": 164310
}
]
fs.js:137
throw new ERR_INVALID_CALLBACK();
^
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
at makeCallback (fs.js:137:11)
at Object.rename (fs.js:571:14)
at /opt/qewd/mapped/startup.js:105:12
at Array.forEach (<anonymous>)
at /opt/qewd/mapped/startup.js:102:17
at Immediate.<anonymous> (/opt/qewd/mapped/node_modules/multer/lib/make-middleware.js:53:37)
at runCallback (timers.js:706:11)
at tryOnImmediate (timers.js:676:5)
at processImmediate (timers.js:658:5)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! qewd-server@2.40.1 start: `NODE_PATH=/opt/qewd/mapped/modules node qewd.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the qewd-server@2.40.1 start script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! /root/.npm/_logs/2019-03-22T23_44_22_018Z-debug.log
fs.rename(file.path, target_path)
fs.rename(file.path, target_path, () => {})
app.use('/static', express.static('upload'))
var config = require('./startup_config.json');
var local_routes = require('./local_routes.json');
var express = require('express')
config.addMiddleware = function(bodyParser, app, q) {
app.use('/static', express.static('upload'))
};
module.exports = {
config: config,
routes: local_routes
};
The file path to the `upload` folder is `/opt/qewd/mapped/upload`. However browsing to this location (which does contain files) gives an error:
http://localhost:8082/static/03.jpg
Cannot GET /static/03.jpg
app.use('/static', express.static('mapped/upload'))
app.use('/', express.static(config.webServerRootPath))
during its setup, so any static files that are under the web server root path can be fetched using a URL relative to the root path
It depends on where you're wanting to save uploaded files.
Rob
Got it working!
app.use('/static', express.static('mapped/upload'))
--
You received this message because you are subscribed to the Google Groups "Enterprise Web Developer Community" group.
To unsubscribe from this group and stop receiving emails from it, send an email to enterprise-web-develope...@googlegroups.com.
To post to this group, send email to enterprise-web-de...@googlegroups.com.
Visit this group at https://groups.google.com/group/enterprise-web-developer-community.
For more options, visit https://groups.google.com/d/optout.
module.exports = function(bodyParser, app, q) {require('dotenv').config();
var multer = require('multer');var fs = require('fs');
var dest_path = process.env.EQAOUT_ROOT + '\\File_uploads\\IRs';var upload = multer({dest: dest_path,limits: {fileSize: 10000000}
// Now send a message to vue-test worker process application handler// to check validity of token
var messageObj = {type: 'authenticate',
application: 'file-upl',token: token};/*q.handleMessage(messageObj, function(response) {
if (response.message.error) {res.status(400);res.send(response.message.error);return;}
});*/// token was valid so Ok to upload file - DEBUGGING
console.log('** authorised ok!');next();}
function uploadFile(req, res, next) {// token was valid so Ok to upload file
// security - first check extensionif (!chkExt(req.file)) {res.status(400);res.send('File type ' + ext + ' not allowed');return;
}var uploadSingle = upload.single('singlefile');uploadSingle(req, res, function (err) {if (err) {// something went wrong with the uploadres.status(400);res.send(response.message.error);return;}
var fldr = req.body.uploadFldr; // folder for that particular IRif (!fldr) fldr = "Error"
// rename the temporary uploaded file using the original file name
var target_path = dest_path + '/' + fldr + '/' + req.file.originalname;console.log(req.file.path + "," + target_path); // DEBUGGINGfs.rename(req.file.path, target_path, function() {if (err) console.log(err);
});res.send('Successfully uploaded!');});}
function uploadFiles(req, res, next) {
var uploadArray = upload.array('multifile', 5);uploadArray(req, res, function (err) {if (err) {// something went wrong with the uploadres.status(400);res.send(response.message.error);return;}console.log('*** files uploaded: ' + JSON.stringify(req.files, null, 2));
var fldr = req.body.uploadFldr; // folder for that particular IRif (!fldr) fldr = "Error"console.log('*** uploaded to folder ' + fldr);req.files.forEach(function(file) {// security - check extensionif (!chkExt(file)) {console.log('File type ' + ext + ' not allowed');return;
}// rename each temporary file
var target_path = dest_path + '/' + fldr + '/' + file.originalname;fs.rename(file.path, target_path, function () {if (err) console.log(err);
});});res.send('Successfully uploaded!');});}
function chkExt(file) { // only some extensions allowed// allowed extensionsvar extList =['txt','pdf','xls','xlsx','doc','docx','jpg','jpeg','png','tif','tiff','gif'];// security - first check extensionvar filenm = file.originalname;var ff = filenm.split("."), ext = ff[ff.length - 1];ext = ext.toLowerCase()return extList.includes(ext);}app.post('/file-upl/upload', [authenticate, uploadFile]);app.post('/file-upl/multiple-files', [authenticate, uploadFiles]);};