httpwsd.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. /* This file is part of AToMPM - A Tool for Multi-Paradigm Modelling
  2. * Copyright 2011 by the AToMPM team and licensed under the LGPL
  3. * See COPYING.lesser and README.md in the root of this project for full details
  4. */
  5. /*********************************** IMPORTS **********************************/
  6. var _util = require("util"),
  7. _fs = require('fs'),
  8. _http = require('http'),
  9. _path = require('path'),
  10. _url = require('url'),
  11. _utils = require('./utils'),
  12. _sio = require('socket.io'),
  13. _cp = require('child_process'),
  14. _fspp = require('./___fs++'),
  15. _duri = require('./___dataurize');
  16. /*********************************** GLOBALS **********************************/
  17. /* an array of WebWorkers
  18. ... each has its own mmmk instance */
  19. var workers = new Array();
  20. /* an array of response objects
  21. ... for workers to write on when they complete requests */
  22. var responses = new Array();
  23. /* a map of worker ids to socket.io socket session ids
  24. ... each socket is registered to exactly one worker
  25. ... several sockets may be registered to the same worker */
  26. var workerIds2socketIds = {};
  27. /************************************ UTILS ***********************************/
  28. /** Remove invalid characters from a string. **/
  29. function __clean_string(s)
  30. {
  31. if (s == undefined) {
  32. return s;
  33. }
  34. s = JSON.stringify(s);
  35. s = s.replace(/'/g, '');
  36. s = s.replace(/"/g, '');
  37. s = s.replace(/‘/g, '');
  38. s = s.replace(/’/g, '');
  39. s = s.replace(/\\/g, '\\');
  40. s = s.replace(/\//g, '\/');
  41. s = s.replace(/\\n/g, ' ');
  42. return s;
  43. }
  44. /** Syntactic sugar to build and send HTTP responses **/
  45. function __respond(response, statusCode, reason, data, headers)
  46. {
  47. response.writeHead(
  48. statusCode,
  49. __clean_string(reason),
  50. (headers || {'Content-Type': 'text/plain',
  51. 'Access-Control-Allow-Origin': '*'}));
  52. var encoding =
  53. (headers &&
  54. (headers['Content-Type'].match(/image/) ||
  55. headers['Content-Type'].match(/pdf/) ||
  56. headers['Content-Type'].match(/zip/) ) ?
  57. 'binary' :
  58. 'utf8'),
  59. content = reason || data;
  60. if( _utils.isObject(content) )
  61. response.end(_utils.jsons(content,null,'\t'), encoding);
  62. else
  63. response.end(content, encoding);
  64. }
  65. /** Syntactic sugar to build and send a socket.io message **/
  66. function __send(socket, statusCode, reason, data, headers)
  67. {
  68. socket.emit('message',
  69. {'statusCode':statusCode,
  70. 'reason':reason,
  71. 'headers':(headers || {'Content-Type': 'text/plain'}),
  72. 'data':data});
  73. }
  74. /************************************ LOGIC ***********************************/
  75. var httpserver = _http.createServer(
  76. function(req, resp)
  77. {
  78. var url = _url.parse(req.url,true);
  79. url.pathname = decodeURI(url.pathname);
  80. /* serve client */
  81. if( req.method == 'GET' && url.pathname == '/atompm' )
  82. _fs.readFile('./client/atompm.html', 'utf8',
  83. function(err, data)
  84. {
  85. if(err)
  86. __respond(resp,500,String(err));
  87. else
  88. __respond(resp,200,'',data,{'Content-Type': 'text/html'});
  89. });
  90. else if( req.method == 'GET' && url.pathname == '/favicon.png' )
  91. _fs.readFile('./favicon.png', 'binary',
  92. function(err, data)
  93. {
  94. if(err)
  95. __respond(resp,500,String(err));
  96. else
  97. __respond(resp,200,'',data,{'Content-Type': 'image/png'});
  98. });
  99. /* provide an interface to the unfortunately unavoidable dataurize
  100. module which returns data URIs for resources at arbitrary URLs */
  101. else if( req.method == 'GET' && url.pathname == '/datauri' )
  102. {
  103. var target = _url.parse(decodeURI(url['query']['target']));
  104. _duri.dataurize(
  105. target,
  106. function(err,datauri)
  107. {
  108. if(err)
  109. __respond(resp,500,_utils.jsons(err));
  110. else
  111. __respond(resp,200,'',datauri);
  112. });
  113. }
  114. /* serve metamodels, buttons models and their icons */
  115. else if( req.method == 'GET' &&
  116. (url.pathname.match(/\.metamodel$/) ||
  117. url.pathname.match(/\.buttons.model$/) ||
  118. url.pathname.match(/\.icon\.png$/i)) )
  119. {
  120. var isIcon = url.pathname.match(/\.icon\.png$/i);
  121. _fs.readFile('./users/'+url.pathname, (isIcon ? 'binary' : 'utf8'),
  122. function(err, data)
  123. {
  124. if(err)
  125. __respond(resp,500,String(err));
  126. else
  127. {
  128. var contentType =
  129. (isIcon ?
  130. {'Content-Type': 'image/png'} :
  131. {'Content-Type': 'application/json'});
  132. __respond(resp,200,'',data,contentType);
  133. }
  134. });
  135. }
  136. /* serve ordinary files (e.g., js includes, images, css)
  137. NOTE:: distinguish between atompm images (e.g., grid background,
  138. filebrowser icons) and CS/Images */
  139. else if( req.method == 'GET' &&
  140. (url.pathname.match(/\.html$/) ||
  141. url.pathname.match(/\.css$/) ||
  142. url.pathname.match(/\.js$/) ||
  143. url.pathname.match(/\.pdf$/) ||
  144. url.pathname.match(/\.png$/i) ||
  145. url.pathname.match(/\.jpg$/i) ||
  146. url.pathname.match(/\.jpeg$/i) ||
  147. url.pathname.match(/\.gif$/i) ||
  148. url.pathname.match(/\.svg$/i)) )
  149. {
  150. var isImage = url.pathname.match(/\.png$/i) ||
  151. url.pathname.match(/\.jpg$/i) ||
  152. url.pathname.match(/\.jpeg$/i) ||
  153. url.pathname.match(/\.gif$/i) ||
  154. url.pathname.match(/\.svg$/i),
  155. isText = ! isImage && ! url.pathname.match(/\.pdf$/);
  156. if( isImage && ! url.pathname.match(/^\/client\/media\//) )
  157. url.pathname = '/users/'+url.pathname;
  158. _fs.readFile('.'+url.pathname, (isText ? 'utf8' : 'binary'),
  159. function(err, data)
  160. {
  161. if(err)
  162. __respond(resp,500,String(err));
  163. else
  164. {
  165. var contentType =
  166. (url.pathname.match(/\.html$/) ?
  167. {'Content-Type': 'text/html'} :
  168. url.pathname.match(/\.css$/) ?
  169. {'Content-Type': 'text/css'} :
  170. url.pathname.match(/\.js$/) ?
  171. {'Content-Type': 'application/javascript'} :
  172. url.pathname.match(/\.pdf$/) ?
  173. {'Content-Type': 'application/pdf'} :
  174. url.pathname.match(/\.png$/i) ?
  175. {'Content-Type': 'image/png'} :
  176. url.pathname.match(/\.jpg$/i) ||
  177. url.pathname.match(/\.jpeg$/i) ?
  178. {'Content-Type': 'image/jpeg'} :
  179. url.pathname.match(/\.gif$/i) ?
  180. {'Content-Type': 'image/gif'} :
  181. url.pathname.match(/\.svg$/i) ?
  182. {'Content-Type': 'image/svg+xml'} :
  183. undefined);
  184. __respond(resp,200,'',data,contentType);
  185. }
  186. });
  187. }
  188. /* serve encrypted user password */
  189. else if( req.method == 'GET' && url.pathname == '/passwd' )
  190. _fs.readFile('./users/'+url['query']['username']+'/passwd', 'utf8',
  191. function(err, data)
  192. {
  193. if(err)
  194. __respond(resp,500,String(err));
  195. else
  196. __respond(resp,200,'',data,{'Content-Type': 'text/html'});
  197. });
  198. /* create new user
  199. 1. make sure user doesn't already exist
  200. 2. make a new copy of ./users/(default)
  201. 3. create password file */
  202. else if( req.method == 'POST' && url.pathname == '/user' )
  203. {
  204. var userdir = './users/'+url['query']['username'];
  205. _fs.exists(userdir,
  206. function(exists)
  207. {
  208. if( exists )
  209. {
  210. __respond(resp,500,'username already exists');
  211. return;
  212. }
  213. _fspp.cp('./users/(default)/',userdir,
  214. function(err, stdout, stderr)
  215. {
  216. if( err )
  217. {
  218. __respond(resp,500,String(err));
  219. return;
  220. }
  221. _fs.writeFile(
  222. userdir+'/passwd',
  223. url['query']['password'],
  224. function(err)
  225. {
  226. if( err )
  227. __respond(resp,500,String(err));
  228. else
  229. __respond(resp,200);
  230. });
  231. });
  232. });
  233. }
  234. /* serve [a subset of] user preferences */
  235. else if( req.method == 'GET' && url.pathname.match(/prefs$/) )
  236. _fs.readFile('./users/'+url.pathname, 'utf8',
  237. function(err, data)
  238. {
  239. if(err)
  240. __respond(resp,500,String(err));
  241. else if( url['query']['subset'] == undefined )
  242. __respond(resp,200,'',data);
  243. else
  244. try
  245. {
  246. __respond(
  247. resp,
  248. 200,
  249. '',
  250. _utils.splitDict(
  251. _utils.jsonp(data),
  252. _utils.jsonp(url['query']['subset'])));
  253. }
  254. catch(err) {__respond(resp,500,String(err));}
  255. });
  256. /* update user preferences
  257. 1 retrieve all post data
  258. 2 read prefs file from disk
  259. 3 apply changes
  260. 4 write updated prefs to disk */
  261. else if( req.method == 'PUT' && url.pathname.match(/prefs$/) )
  262. {
  263. var reqData = '';
  264. req.addListener("data", function(chunk) {reqData += chunk;});
  265. req.addListener("end",
  266. function()
  267. {
  268. _fs.readFile('./users/'+url.pathname, 'utf8',
  269. function(err, prefs)
  270. {
  271. if(err)
  272. __respond(resp,500,String(err));
  273. else
  274. {
  275. try
  276. {
  277. prefs = _utils.jsonp(prefs);
  278. reqData = _utils.jsonp(reqData);
  279. }
  280. catch(err)
  281. {
  282. __respond(resp,500,String(err));
  283. return;
  284. }
  285. for( var pref in reqData )
  286. prefs[pref]['value'] = reqData[pref];
  287. _fs.writeFile(
  288. './users/'+url.pathname,
  289. _utils.jsons(prefs,null,'\t'),
  290. function(err, data)
  291. {
  292. if(err)
  293. __respond(resp,500,String(err));
  294. else
  295. __respond(resp,200);
  296. });
  297. }
  298. });
  299. });
  300. }
  301. /* delete specified file/folder */
  302. else if( req.method == 'DELETE' && url.pathname.match(/\.(file|folder)$/) )
  303. {
  304. if (url.pathname.match('_Trash_')) {
  305. __respond(resp,500,"cannot remove trash!");
  306. } else {
  307. var matches = url.pathname.match(/^\/(.*?)\/(.*\/)?(.*)\.(file|folder)$/),
  308. username = matches[1],
  309. folder = matches[2] || '',
  310. fname = matches[3],
  311. userdir = './users/'+username+'/',
  312. ondelete =
  313. function(err, stdout, stderr)
  314. {
  315. if( err )
  316. __respond(resp,500,String(err));
  317. else
  318. __respond(resp,200);
  319. },
  320. deletef =
  321. function(response)
  322. {
  323. var newname = userdir+'_Trash_/'+folder+fname;
  324. if (_fs.existsSync(newname)) {
  325. if (url.pathname.match(/\.folder$/)) {
  326. _fspp.deleteFolderRecursive(newname);
  327. } else {
  328. _fs.unlink(newname);
  329. }
  330. }
  331. _fspp.mv(userdir+folder+fname,userdir+'_Trash_/'+folder,ondelete);
  332. };
  333. _fs.exists(userdir+'_Trash_/'+folder,
  334. function(exists)
  335. {
  336. if( ! exists )
  337. _fspp.mkdirs(userdir+'_Trash_/'+folder,deletef);
  338. else {
  339. deletef();
  340. }
  341. });
  342. }
  343. }
  344. /* create folder */
  345. else if( req.method == 'POST' && url.pathname.match(/\.folder$/) )
  346. {
  347. var matches = url.pathname.match(/^\/(.*?)\/(.*)\.folder$/),
  348. username = matches[1],
  349. folder = matches[2],
  350. userdir = './users/'+username+'/',
  351. oncreate =
  352. function(err, stdout, stderr)
  353. {
  354. if( err )
  355. __respond(resp,500,String(err));
  356. else
  357. __respond(resp,200);
  358. };
  359. _fs.exists(userdir+folder,
  360. function(exists)
  361. {
  362. if( ! exists )
  363. _fspp.mkdirs(userdir+folder,oncreate);
  364. else {
  365. oncreate("folder " + folder + " already exists");
  366. }
  367. });
  368. }
  369. /* rename file/folder (or move) */
  370. else if( req.method == 'PUT' && url.pathname.match(/\.(folder|file)$/) )
  371. {
  372. req.setEncoding('utf8');
  373. var data = '';
  374. req.addListener("data", function(chunk) {data += chunk;});
  375. req.addListener("end",
  376. function() {
  377. data = _utils.jsonp(data);
  378. if (data.match(/^\//)) {
  379. // move
  380. var matches = url.pathname.match(/^\/(.*?)\/(.*\/)?(.*)\.(file|folder)$/),
  381. username = matches[1],
  382. folder = matches[2] || '',
  383. fname = matches[3],
  384. userdir = './users/'+username,
  385. onmove =
  386. function(err, stdout, stderr)
  387. {
  388. if( err )
  389. __respond(resp,500,String(err));
  390. else
  391. __respond(resp,200);
  392. };
  393. if (_fs.existsSync(userdir+data+fname)) {
  394. if (url.pathname.match(/\.folder$/)) {
  395. _fspp.deleteFolderRecursive(userdir+data+fname);
  396. } else {
  397. _fs.unlink(newname);
  398. }
  399. }
  400. _fspp.mv(userdir+"/"+folder+fname,userdir+data,onmove);
  401. } else {
  402. // rename
  403. var matches = url.pathname.match(/^\/(.*?)\/(.*\/)?(.*)\.(file|folder)$/),
  404. username = matches[1],
  405. folder = matches[2] || '',
  406. fname = matches[3],
  407. userdir = './users/'+username+'/',
  408. onrename =
  409. function(err, stdout, stderr)
  410. {
  411. if( err )
  412. __respond(resp,500,String(err));
  413. else
  414. __respond(resp,200);
  415. };
  416. _fs.rename(userdir+folder+fname,userdir+folder+data,onrename);
  417. }
  418. }
  419. );
  420. }
  421. else if (req.method == 'POST' && url.pathname.match(/\.formalism$/)) {
  422. // create new formalism
  423. var matches = url.pathname.match(/^(.*)\/(.*)\.formalism$/),
  424. username = matches[1],
  425. formalism = matches[2],
  426. userdir = './users/'+username+"/",
  427. oncreatefolder =
  428. function(err, stdout, stderr)
  429. {
  430. if( err )
  431. __respond(resp,500,String(err));
  432. else {
  433. _fs.createReadStream(userdir+"Formalisms/__Templates__/MetamodelTemplate.model").pipe(_fs.createWriteStream(userdir+"Formalisms/"+formalism+"/"+formalism+".model"));
  434. _fs.createReadStream(userdir+"Formalisms/__Templates__/ConcreteSyntaxTemplate.model").pipe(_fs.createWriteStream(userdir+"Formalisms/"+formalism+"/"+formalism+".defaultIcons.model"));
  435. _fs.createReadStream(userdir+"Formalisms/__Templates__/MetamodelTemplate.metamodel").pipe(_fs.createWriteStream(userdir+"Formalisms/"+formalism+"/"+formalism+".metamodel"));
  436. _fs.createReadStream(userdir+"Formalisms/__Templates__/ConcreteSyntaxTemplate.defaultIcons.metamodel").pipe(_fs.createWriteStream(userdir+"Formalisms/"+formalism+"/"+formalism+".defaultIcons.metamodel"));
  437. _fs.createReadStream(userdir+"Formalisms/__Templates__/T_TransformationTemplate.model").pipe(_fs.createWriteStream(userdir+"Formalisms/"+formalism+"/OperationalSemantics/T_OperationalSemantics.model"));
  438. _fs.createReadStream(userdir+"Formalisms/__Templates__/T_TransformationTemplate.model").pipe(_fs.createWriteStream(userdir+"Formalisms/"+formalism+"/TranslationalSemantics/T_TranslationalSemantics.model"));
  439. __respond(resp,200);
  440. }
  441. };
  442. _fs.mkdir(userdir+"Formalisms/"+formalism,function(err, stdout, stderr) {
  443. if( err )
  444. __respond(resp,500,String(err));
  445. else {
  446. _fs.mkdirSync(userdir+"Formalisms/"+formalism+"/OperationalSemantics");
  447. _fs.mkdir(userdir+"Formalisms/"+formalism+"/TranslationalSemantics", oncreatefolder);
  448. }
  449. });
  450. }
  451. else if (req.method == 'POST' && url.pathname.match(/\.transformation$/)) {
  452. // create new transformation
  453. var matches = url.pathname.match(/^\/(.*?)\/(.*)\.transformation$/),
  454. username = matches[1],
  455. userdir = './users/'+username+"/";
  456. _fs.createReadStream(userdir+"Formalisms/__Templates__/T_TransformationTemplate.model").pipe(_fs.createWriteStream('./users/'+url.pathname.slice(0, -(".transformation".length))));
  457. __respond(resp,200);
  458. }
  459. else if (req.method == 'POST' && url.pathname.match(/\.rule$/)) {
  460. // create new rule
  461. var matches = url.pathname.match(/^\/(.*?)\/(.*)\.rule$/),
  462. username = matches[1],
  463. userdir = './users/'+username+"/";
  464. _fs.createReadStream(userdir+"Formalisms/__Templates__/R_RuleTemplate.model").pipe(_fs.createWriteStream('./users/'+url.pathname.slice(0, -(".rule".length))));
  465. __respond(resp,200);
  466. }
  467. /* extract user-uploaded archive to specified folder
  468. 1. read in all data
  469. 2. make sure destination exists and is a directory
  470. 3. write data to temp file (upload###.zip)
  471. 4. extract temp file and remove it
  472. NOTE:: it's not clear why (despite hours of googling) but the
  473. "req.setEncoding('utf8')" statement makes the difference
  474. between retrieving correct and corrupted (when non-text
  475. files in zip) data */
  476. else if( req.method == 'PUT' && url.pathname.match(/\.file$/) )
  477. {
  478. req.setEncoding('utf8');
  479. let reqData = '',
  480. tmpzip = 'upload'+Date.now()+'.zip',
  481. destdir = './users/'+url.pathname.match(/(.*)\.file$/)[1]+'/';
  482. if( url.pathname.contains("..") || url.pathname.contains(";") ) {
  483. __respond(resp, 404,
  484. 'invalid pathname, no semicolons or .. allowed :: ' + url.pathname);
  485. return;
  486. }
  487. req.addListener("data", function(chunk) {reqData += chunk;});
  488. req.addListener("end",
  489. function()
  490. {
  491. _fs.stat(destdir,
  492. function(err,stats)
  493. {
  494. if( err )
  495. __respond(resp,404,String(err));
  496. else if( ! stats.isDirectory() )
  497. __respond(resp,404,
  498. 'destination is not a directory :: '+destdir);
  499. else
  500. _fs.writeFile(
  501. destdir+tmpzip,eval('('+reqData+')'),
  502. 'binary',
  503. function(err)
  504. {
  505. _cp.exec('cd '+destdir+'; unzip -o '+tmpzip,
  506. function(err, stdout, stderr)
  507. {
  508. if( err )
  509. __respond(resp,500,String(err));
  510. else
  511. __respond(resp,200);
  512. _fs.unlink(destdir+tmpzip, function(err){
  513. console.log("Unlinked " + destdir + tmpzip);
  514. }
  515. );
  516. });
  517. });
  518. });
  519. });
  520. }
  521. /* serve specified file/folder within a zip file */
  522. else if( req.method == 'GET' && url.pathname.match(/\.file$/) )
  523. {
  524. let matches = url.pathname.match(/^\/(.*?)\/(.*)\.file$/),
  525. username = matches[1],
  526. fname = './'+matches[2],
  527. userdir = './users/'+username+'/',
  528. tmpzip = 'download'+Date.now()+'.zip';
  529. if( username.contains("..") || username.contains(";") ) {
  530. __respond(resp, 404,
  531. 'invalid username, no colons or .. allowed :: ' + username);
  532. return;
  533. }
  534. _fs.exists(userdir+fname,
  535. function(exists)
  536. {
  537. if( ! exists )
  538. __respond(resp,404,
  539. 'requested file/folder does not exist :: '+fname);
  540. else
  541. _cp.exec('cd '+userdir+'; zip -r '+tmpzip+' "'+fname+'"',
  542. function(err, stdout, stderr)
  543. {
  544. if( err )
  545. __respond(resp,500,String(err));
  546. else
  547. _fs.readFile(userdir+tmpzip,
  548. function(err, data)
  549. {
  550. __respond(resp,200,'',data,
  551. {'Content-Type':'application/zip',
  552. 'Content-Disposition':
  553. 'attachment; filename="'+tmpzip+'"'});
  554. _fs.unlink(userdir+tmpzip, function(err){
  555. console.log("Unlinked " + userdir+tmpzip);
  556. });
  557. });
  558. });
  559. });
  560. }
  561. /* serve list of all files */
  562. else if( req.method == 'GET' &&
  563. url.pathname.match(/^\/.+\/filelist$/) )
  564. {
  565. var matches = url.pathname.match(/^\/(.+)\/filelist$/);
  566. _fspp.findfiles('./users/'+matches[1],
  567. function(err, stdout, stderr)
  568. {
  569. if( err )
  570. __respond(resp,404,String(err));
  571. else
  572. __respond(resp,200,'',stdout);
  573. });
  574. }
  575. /* spawn new worker */
  576. else if( (url.pathname == '/csworker' || url.pathname == '/asworker')
  577. && req.method == 'POST' )
  578. {
  579. /* setup and store new worker */
  580. var worker = _cp.fork(_path.join(__dirname, '__worker.js')),
  581. wid = workers.push(worker)-1;
  582. workerIds2socketIds[wid] = [];
  583. worker.on('message',
  584. function(msg)
  585. {
  586. /* push changes (if any) to registered sockets... even empty
  587. changelogs are pushed to facilitate sequence number-based
  588. ordering */
  589. if( msg['changelog'] != undefined )
  590. {
  591. var _msg = {'changelog':msg['changelog'],
  592. 'sequence#':msg['sequence#'],
  593. 'hitchhiker':msg['hitchhiker']};
  594. workerIds2socketIds[wid].forEach(
  595. function(sid)
  596. {
  597. __send(
  598. wsserver.sockets.sockets.get(sid),
  599. undefined,
  600. undefined,
  601. _msg);
  602. });
  603. }
  604. /* respond to a request */
  605. if( msg['respIndex'] != undefined )
  606. __respond(
  607. responses[msg['respIndex']],
  608. msg['statusCode'],
  609. msg['reason'],
  610. JSON.stringify(
  611. {'headers':
  612. (msg['headers'] ||
  613. {'Content-Type': 'text/plain',
  614. 'Access-Control-Allow-Origin': '*'}),
  615. 'data':msg['data'],
  616. 'sequence#':msg['sequence#']}),
  617. {'Content-Type': 'application/json'});
  618. });
  619. worker.send(
  620. {'workerType':url.pathname,
  621. 'workerId':wid});
  622. /* respond worker id (used to identify associated worker) */
  623. __respond(
  624. resp,
  625. 201,
  626. '',
  627. ''+wid);
  628. return;
  629. }
  630. /* check for worker id and it's validity */
  631. else if( url['query'] == undefined ||
  632. url['query']['wid'] == undefined )
  633. __respond(resp, 400, 'missing worker id');
  634. else if( workers[url['query']['wid']] == undefined )
  635. __respond(resp, 400, 'invalid worker id :: '+url['query']['wid']);
  636. /* save resp object and forward request to worker (if request is PUT or
  637. POST, recover request data first)
  638. TBI:: only registered sockets should be allowed to speak to worker
  639. ... one way of doing this is forcing request urls to contain
  640. cid=socket.id## */
  641. else if( req.method == 'PUT' || req.method == 'POST' )
  642. {
  643. var reqData = '';
  644. req.addListener("data", function(chunk) {reqData += chunk;});
  645. req.addListener("end",
  646. function()
  647. {
  648. workers[url['query']['wid']].send(
  649. {'method':req.method,
  650. 'uri':url.pathname,
  651. 'reqData':(reqData == '' ?
  652. undefined :
  653. eval('('+reqData+')')),
  654. 'uriData':url['query'],
  655. 'respIndex':responses.push(resp)-1});
  656. });
  657. }
  658. else
  659. workers[url['query']['wid']].send(
  660. {'method':req.method,
  661. 'uri':url.pathname,
  662. 'uriData':url['query'],
  663. 'respIndex':responses.push(resp)-1});
  664. });
  665. let port = 8124;
  666. httpserver.listen(port);
  667. console.log("AToMPM listening on port: " + port)
  668. let wsserver = new _sio.Server(httpserver);
  669. wsserver.sockets.on('connection',
  670. function(socket)
  671. {
  672. /* unregister this socket from the specified worker ... when a worker
  673. has no more registered sockets, terminate it */
  674. function unregister(wid)
  675. {
  676. var i = workerIds2socketIds[wid].indexOf(socket.id);
  677. if( i == -1 )
  678. __send(socket,403,'already unregistered from worker');
  679. else
  680. {
  681. workerIds2socketIds[wid].splice(i,1);
  682. if( workerIds2socketIds[wid].length == 0 )
  683. {
  684. workers[wid].kill();
  685. workers[wid] = undefined;
  686. delete workerIds2socketIds[wid];
  687. }
  688. __send(socket,200);
  689. }
  690. }
  691. /* onmessage : on reception of data from client */
  692. socket.on('message',
  693. function(msg/*{method:_,url:_}*/)
  694. {
  695. var url = _url.parse(msg.url,true);
  696. /* check for worker id and it's validity */
  697. if( url['query'] == undefined ||
  698. url['query']['wid'] == undefined )
  699. return __send(socket,400,'missing worker id');
  700. var wid = url['query']['wid'];
  701. if( workers[wid] == undefined )
  702. __send(socket,400,'unknown worker id :: '+wid);
  703. /* register socket for requested worker */
  704. else if( msg.method == 'POST' &&
  705. url.pathname.match(/changeListener$/) )
  706. {
  707. if( workerIds2socketIds[wid].indexOf(socket.id) > -1 )
  708. __send(socket,403,'already registered to worker');
  709. else
  710. {
  711. workerIds2socketIds[wid].push(socket.id);
  712. __send(socket,201);
  713. }
  714. }
  715. /* unregister socket for requested worker */
  716. else if( msg.method == 'DELETE' &&
  717. url.pathname.match(/changeListener$/) )
  718. unregister(wid);
  719. /* unsupported request */
  720. else
  721. __send(socket,501);
  722. });
  723. /* ondisconnect : on disconnection of socket */
  724. socket.on('disconnect',
  725. function()
  726. {
  727. for( var wid in workerIds2socketIds )
  728. for( var i in workerIds2socketIds[wid] )
  729. if( workerIds2socketIds[wid][i] == socket.id )
  730. {
  731. unregister(wid);
  732. return;
  733. }
  734. });
  735. });