-
Notifications
You must be signed in to change notification settings - Fork 0
/
session.js
321 lines (288 loc) · 8.72 KB
/
session.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/**
* Cloudspokes challenge #1840 - session handler middleware
*
* session.js
*
* Middleware for simple session management, keeping track of the files uploaded
* by the user.
*
* Uses mongoDB as database and mongojs as driver.
*
* Uses node JS child_processes taking advantage of scalable, cloud-based
* services.
*
* @author [email protected]
* @version
*/
// mongodb access parameters, set by mongolab heroku add-on or locally
var databaseUri = process.env.MONGOLAB_URI || 'csc1840',
collections = ['visitors'];
// going to use these...
var db = require('mongojs').connect(databaseUri, collections),
ObjectId = require('mongojs').ObjectId,
crypto = require('crypto'),
q = require('q'),
pusher = require('./pusher.js'),
cp = require('child_process'),
path = require('path');
/**
* preroute
*
* As there is no real user management (register/login) present in the
* application, this simple middleware is used for session data handing.
*
* First time a user connects to the application/page, he/she gets a USERID
* that will be used to create a channel of messages the user subscribes to,
* to recieve various server/application events.
*
* @param {object} the request object containing the session
* @param {object} the response object
* @param {function} the next middleware in the route
*/
exports.preroute = function(req, res, next){
if(typeof req.session.channelId == 'undefined')
{
// check if was page refresh
var find = q.defer();
db.visitors.find({
sessionId : req.session.id
}, find.makeNodeResolver());
find.promise.then(function(visitors) {
if (visitors.length === 0) {
// not page refresh, does not have a session/channel
// generate a random channel
var channelId = getRandomId();
var defaultImage = [{
'url': 'woman.jpg',
'title': 'Sample'
}];
// save in the db
var save = q.defer();
db.visitors.save({ sessionId: req.session.id,
channel: channelId, images: defaultImage },
save.makeNodeResolver());
save.promise.then(function(saved) {
// new session registered...
req.session.channelId = channelId;
next();
}, function(err) {
// DB error...
// status code 503 : Service Unavailable
res.status(503).end("FAILED TO SAVE SESSION");
});
} else {
// user already has a session
req.session.channelId = visitor.channelId;
}
}, function(err) {
// DB error...no grace at all!
// status code 503 : Service Unavailable
res.status(503).end("FAILED TO FETCH USER DB");
});
}else{
// already has session
next();
}
}
/**
* Authentication backend
*
* Authenticates user checking mongodb against channel_name.
* Sets the response content to 200 OK || 401 Not authorized || 500 Server error
*/
exports.auth = function (req, res) {
// prefix the channel as private
var channel = req.body.channel_name.replace('private-', '');
// Authenticate using stored mongodb values...
var find = q.defer();
db.visitors.find({
channel : channel
}, find.makeNodeResolver());
// resolved promise
find.promise.then(function(visitors) {
if (visitors.length === 1) {
// we have a visitor!
var authObj = pusher.auth(req.body.socket_id, req.body.channel_name);
res.status(200).json(authObj);
} else if (visitors.length === 0) {
// not registered...
// authentication failed...
// status code 401 : Unauthorized
res.status(401).end();
} else {
// ILLEGAL! CORRUPTED DATABASE
// the email can't appear more than once in the collection!
// status code 500 : Internal Server Error
res.status(500).end();
};
}, function(err) {
// DB error...
// status code 503 : Service Unavailable
res.status(503).end();
});
};
/**
* handshake
*
* last confirmation before actual data transfer begins
*/
exports.handshake = function (req, res){
var visitor = req.get('X-Visitor');
var channel = 'private-'+visitor;
if( req.session.channelId === visitor ){
// greet the user
pusher.trigger( channel, 'server-greet', {msg:'Welcome!'});
res.status(200).send('Hello');
queryImages(req.session.channelId );
}else{
res.send(401,'Who are You stranger?').end();
}
};
/**
* queryImages
*
* searches already uploaded images in the users session history
*
* @param {string} the id of the ongoing session
* @return {object} the result of the query
*/
var queryImages = function (channelId) {
// check if we have already a session with this id...(probably not)
var find = q.defer();
db.visitors.find({
channel : channelId
},find.makeNodeResolver());
find.promise.then(function(visitors) {
if (visitors.length === 1) {
var visitor = visitors[0];
pusher.trigger( 'private-'+channelId, 'server-uploaded-images',
{ data: visitor.images, msg: 'Images recovered' });
} else {
// something got badly messed up!
// DB error...
// status code 500 : Internal error
pusher.trigger( 'private-'+channelId, 'server-error', {status:500});
}
}, function(err) {
// DB error...
// status code 503 : Service Unavailable
pusher.trigger( 'private-'+channelId, 'server-error', {status:500});
});
};
/**
* fileupload
*
* Uploads the file into target directory, saves the reference in users db entry
* and triggers event on pusher notifing
*/
exports.fileupload = function(req, res){
// get the 'secret' channel
var visitor = req.get('X-Visitor');
var channel = 'private-'+visitor;
// let the visitor know, the server is doing stuff...
pusher.trigger( channel, 'server-info',
{msg:'Upload started...'});
// search for the visitor in the database - promise
var find = q.defer();
db.visitors.find({
sessionId : req.session.id
}, find.makeNodeResolver());
// resolved promise
find.promise.then(function(visitors) {
if (visitors.length == 1) {
// get selected visitor
var visitor = visitors[0];
// get possible images already set
var imagesNow = visitor.images;
// get just uploaded filename
var url = req.files.image.path;
var upstreamName = url.substring(url.lastIndexOf('/')+1);
imagesNow.push( {url: upstreamName} );
// save img location in the db
var save = q.defer();
db.visitors.update({sessionId:req.session.id},
{ $set : {images: imagesNow }}, save.makeNodeResolver());
save.promise.then(function(saved) {
// updated images of user
pusher.trigger( channel, 'server-update',
{status:200, url:upstreamName});
pusher.trigger( channel, 'server-info',
{msg:'Uploaded image.'});
res.status(200).end();
}, function(err) {
// DB error...
// status code 503 : Service Unavailable
res.status(503).end();
});
} else {
// DB error...
// status code 503 : Service Unavailable
res.status(503).end();
}
});
};
/**
* opencv
*
* The iris segmentation class (wrapper)
*
* Runs in two phases, first it detects the Region of Interest, and then
* works with the encountered ROIs through a series of filters to finally
* extract the circle corresponding to the iris.
*
*/
exports.opencv = function(req, res){
var src = req.query.src;
// calculate the absolute path of the image
var fileName = path.basename(src);
var absSrc = path.resolve('./public/data/upload/' + fileName);
var visitor = req.get('X-Visitor');
var channel = 'private-'+visitor;
// now THIS is where the magic starts...
// non-blocking child processes!
pusher.trigger( channel, 'server-info', {msg:'Forking OpenCV...'});
var openCV_fork = cp.fork(__dirname + '/opencv.js');
// 'working' signal to the client untill child process is done
var clicker = setInterval(function(){
pusher.trigger( channel, 'server-info', {msg:'Working...'});
},3500);
// sending notifications to the client...
openCV_fork.on('message', function(m) {
console.log('SESSION got message:', m);
if( m.info ) pusher.trigger( channel, 'opencv-info', {msg:m.info});
if( m.roi ) pusher.trigger( channel, 'opencv-result', {roi:m.roi});
if( m.iris ) {
clearInterval(clicker);
pusher.trigger( channel, 'opencv-result', {iris:m.iris});
// make sure to kill [SIGTERM:15] the forked process after it is done
openCV_fork.disconnect();
};
if( m.error ){
clearInterval(clicker);
pusher.trigger( channel, 'opencv-info', {msg:m.error});
openCV_fork.disconnect();
}
});
//start the heavy calculation in a child process
// this way the server will still be able to handle other requests.
openCV_fork.send({ src: absSrc, channel:channel});
// errors are handled elsewhere...
res.status(200).end();
}
/**
* getRandomId
*
* Simple random ID generator using 'crypto'
*
* @return {string} 64 bytes long random id, should be enough for now...
*/
function getRandomId(){
// syncronous calling crypto...
var buf;
try {
buf = crypto.randomBytes(32).toString('hex');
} catch (ex) {
// handle error
}
return buf;
};