UNPKG

all-node-oracle

Version:

A NodeJS and Oracle DB integration, NodeJS act as http gateway for plsql server pages

480 lines (429 loc) 14.7 kB
var sysCfg = require('./cfg.js') , inspect = require('util').inspect , ReqBase = require('./ReqBase.js') , urlParse = require('url').parse , urlEncoded = require('./util/urlEncoded.js') , mergeHeaders = require('./util/util.js').mergeHeaders , mergeAdd = require('./util/util.js').mergeAdd , override = require('./util/util.js').override , noop = require('./util/util.js').override , debug = require('debug')('noradle:servlet') , debugReq = require('debug')('noradle:reqSocket') , CRLF = '\r\n' , curFeedbackSeq = 0 , fbBuffer = {} , cssBuffer = {} , upload = require('./middleware/upload.js') , zip = require('./middleware/zip.js') , chooseZip = zip.chooseZip , zipFilter = zip.zipFilter , MD5CalcFilter = require('./middleware/MD5Calc.js') , ResultSetsFilter = require('./middleware/ResultSetsFilter.js') , Cache = require('./middleware/Cache.js') , monitor = require('./poolMonitor.js') , stat = {allCnt : 0, reqCnt : 0, cssCnt : 0, fbCnt : 0} , sessionStore = require('./session.js') , error_pages = require('./error_pages.js') , ensureSid = require('./middleware/SessionGuard/ensureSID.js') ; function FBItem(status, headers){ this.status = status; this.headers = headers; this.chunks = []; } function CSSItem(status, headers){ this.status = status; this.headers = headers; this.chunks = []; } function parseCookie(req){ var cookies = {} , hdrCookie = req.headers.cookie , nv ; hdrCookie && hdrCookie.split(/[;,] */).forEach(function(cookie){ nv = cookie.split(/\s*=\s*/); cookies[nv[0]] = nv[1]; }); delete req.headers.cookie; return cookies; } module.exports = function(dbPool, ReqBaseC, customCfg){ var cfg = override(sysCfg, customCfg || {}); return function(req, res, next){ next = next || noop; pspdweb(req, res, next); return; try { pspdweb(req, res, next); } catch (e) { res.writeHead(500, {'Content-Type' : 'text/html'}); res.write('PSP.WEB middleware exception:<pre>'); res.write(inspect(e)); res.end('</pre>'); } }; function pspdweb(req, res, next){ var reqNo = ++stat.allCnt , reqUrl = req.originUrl || req.url , normalReq , cookies , oraSock /* todo: to be removed */ , finCB /* too: to be removed */ , rb , fbId , fbItem , cssmd5 , cssItem , bakRes = res , compress , buffered_response = false , md5val , cachedEntity , md5Content , finished = false , ohdr = { 'server' : 'nodejs', 'x-powered-by' : 'Noradle - PSP.WEB' } , session ; debug('-> %d %s', reqNo, reqUrl); if (urlParse(reqUrl).pathname === cfg.status_url) { monitor.showStatus(req, res, next); return; } // res.socket.setNoDelay(); // todo: to be removed from NGW of PLSQL servlet if (reqUrl === '/favicon.ico') { if (cfg.favicon_url) { res.writeHead(301, {'Location' : cfg.favicon_url}); res.end(); } else { next(); } return; } cookies = req.cookies = parseCookie(req); // give oracle request headers rb = new (ReqBaseC || ReqBase)(req, res, next); rb.done || mergeAdd(rb, new ReqBase(req, res, next)); delete rb.done; function sendResponse(item){ res.writeHead(item.status, item.headers); item.chunks.forEach(function(chunk){ res.write(chunk); }); res.end(); } switch (rb.x$prog) { case 'feedback_b' : stat.fbCnt++; fbId = parseInt(rb.u$qstr.split('=')[1]); fbItem = fbBuffer[fbId]; if (fbItem) { delete fbBuffer[fbId]; sendResponse(fbItem); } else { if (req.headers['if-none-match']) { res.writeHead(304, {}); res.end(); } else if (!fbItem) { res.writeHead(500, {}); res.end('too long after the page redirecting to this feedback page send the request, the feedback is removed from server after timeout'); } else { res.writeHead(400, {}); res.end('please donnot refresh the feedback page manually!'); } } return; case 'css_b' : if (rb.u$spath.search(/\./) >= 0) { next(); return; } stat.cssCnt++; cssmd5 = rb.u$spath; cssItem = cssBuffer[cssmd5]; if (cssItem) { // todo: need to lookup the oraSock status, if it's really free at the moment, or timeout by monitor delete cssBuffer[cssmd5]; sendResponse(cssItem); } else { if (req.headers['if-none-match']) { res.writeHead(304, {}); res.end(); } else if (oraSock === false) { res.writeHead(500, {}); res.end('too long after the page owning the css send the request, the css is removed from server after timeout'); return; } res.writeHead(400, {'x-no-css' : 'no-css-item'}); res.end('please donnot refresh the css manually!'); } return; default: // allow static file with same url prefix with plsql servlet if (!rb.x$dbu || !rb.x$prog || rb.x$dbu.search(/\./) >= 0 || !rb.x$prog.match(/^\w+_\w(\.\w+)?$/)) { next(); return; } } stat.reqCnt++; normalReq = true; if (ensureSid(req, res, bakRes, rb, ohdr, cfg) === false) { return; } req.on('close', function(){ if (!finished) { debugReq(req.url, 'req.onClose', 'client aborted', req.socket.readable, req.socket.writable, req.socket == res.socket); // todo: need to signal later queue to abort interrupter.abort(); } }); if (false) { req.socket.on('end', function(){ if (!finished) { debugReq(req.url, 'req.socket.onEnd', 'client aborted', req.socket.readable, req.socket.writable); } }); req.socket.on('error', function(err){ if (!finished) { debugReq(req.url, 'req.socket.onError', 'client aborted', err, req.socket.readable, req.socket.writable); } }); req.socket.on('close', function(had_error){ if (!finished) { debugReq(req.url, 'req.socket.onClose', 'client aborted', had_error, req.socket.readable, req.socket.writable); } }); } var interrupter = dbPool.findFree(reqUrl, null, function(err, oraReq){ if (err) { (error_pages.onNoFreeConnection(res))(err); return; } oraReq.once('response_timeout', error_pages.onResponseTimeout(res)); oraReq.once('socket_released', error_pages.onSocketReleased(res)); sendRequest(); function sendRequest(){ oraReq.init('HTTP', rb.y$hprof || ''); // 1. http request headers, pass throuth to oracle except for cookies if ("h$" in rb) { // already set in preprocessor, after discarding/spliting some headers delete rb.h$; } else { // add as real http headers send to node oraReq.addHeaders(req.headers, 'h$'); } // 2. http request header's cookies if ("c$" in rb) { // already set in preprocessor, after discarding/spliting some headers delete rb.c$; } else { // add as real http header cookies send to node oraReq.addHeaders(cookies, 'c$'); } // 3.basic http request key-values var parts = rb.x$prog.split('.'); if (parts.length === 1) { rb.x$pack = ''; rb.x$proc = parts[0]; } else { rb.x$pack = parts[0]; rb.x$proc = parts[1]; } oraReq.addHeaders(rb, ''); // 4. parameters, for method=get from querystring, for method=post from body oraReq.addHeaders(urlEncoded(urlParse(req.url).query), ''); // 5. session data // todo need app, bsid, encoding if (rb.x$app && rb.c$BSID && (session = sessionStore.gets(rb.x$app, rb.c$BSID))) { session.store.IDLE = (Date.now() - session.LAT); session.LAT = Date.now(); oraReq.addHeaders(session.store, 's$'); } if (req.method === 'POST') { var req_mime = req.headers['content-type'].split(';')[0]; switch (req_mime) { case 'application/x-www-form-urlencoded' : req.setEncoding('ascii'); (function(){ var bdy = ''; req.on('data', function(chunk){ bdy += chunk; }); req.on('end', function(){ oraReq.addHeaders(urlEncoded(bdy), ''); oraReq.end(onResponse); }); })(); break; case 'multipart/form-data' : upload(req, oraReq, onResponse, next);// todo: break; default: if ('y$instream' in rb) { oraReq.end(onResponse); req.on('data', function(chunk){ oraReq.write(chunk); }); // todo: how to mark end of request body } else if ('content-length' in req.headers) { if ('h$content-length' in rb) ; else { oraReq.addHeader('h$content-length', req.headers['content-length']); } oraReq.end(onResponse); req.on('data', function(chunk){ oraReq.write(chunk); }); } else { (function(){ var chunks = [], cLen = 0; req.on('data', function(chunk){ chunks.push(chunk); cLen += chunk.length; }); req.on('end', function(){ oraReq.addHeader('h$content-length', cLen.toString()); oraReq.end(onResponse); for (var i = 0, len = chunks.length; i < len; i++) { oraReq.write(chunks[i]); } }); })(); } } } else { // http get oraReq.end(onResponse); } req.on('close', function(){ debugReq('client req close', reqUrl); }); } var step = 1; function onResponse(oraRes){ // maybe first response, maybe following response var status = oraRes.status , headers = oraRes.headers , cLen = parseInt(headers['Content-Length']) , flags = {headFixed : true} ; if (oraReq.follows.length > 0) { oraReq.follows.shift(); step--; oraRes.on('end', function(){ fin(); }); if (fbId) { headers['ETag'] = '"' + fbId + '"'; headers['Cache-Control'] = 'max-age=600'; fbItem = fbBuffer[fbId] = new FBItem(status, headers); oraRes.on('data', function(data){ fbItem.chunks.push(data); }); } if (cssmd5) { headers['ETag'] = '"' + cssmd5 + '"'; headers['Cache-Control'] = 'max-age=60000'; cssItem = cssBuffer[cssmd5] = new CSSItem(status, headers); oraRes.on('data', function(data){ cssItem.chunks.push(data); }); } return; } if ('s$BSID' in headers) { if (headers.s$BSID) { // if oracle servlet create new session with unique BSID session = sessionStore.create(rb.x$app, headers.s$BSID); } else { // if oracle servlet want to destroy session store session = sessionStore.destroy(rb.x$app, rb.c$BSID); } delete headers.s$BSID; } // move session setting data to session store if (session) { for (n in headers) { if (n.substr(0, 2) == 's$') { session.set([n.substr(2)], headers[n]); delete headers[n]; } } } // todo: cookies conflict need coverage test mergeHeaders(ohdr, headers); // cause feedback to follow in the same oraSock if (ohdr.Location && ohdr.Location === 'feedback_b?id=') { fbId = ++curFeedbackSeq; ohdr.Location += fbId; res.writeHead(status, ohdr); oraReq.follows = ['feedback']; return; } // cause css link to follow in the same oraSock if (cssmd5 = ohdr['x-css-md5']) { res.writeHead(status, ohdr); oraReq.follows = ['css']; step++; // -- on oraRes.end } // todo: cache will be redesigned if (normalReq && cfg.use_gw_cache && ohdr['ETag'] && status != 304) { md5val = ohdr['ETag'].substr(1, 24); if (cachedEntity = Cache.findCacheHit(req.url, md5val)) { oraSock.write('Cache Hit' + CRLF); cachedEntity.respond(req, res); finCB(null); return; } else { oraSock.write('Cache Miss' + CRLF); // todo : save http response header and body in cache md5Content = new Cache.MD5Content(md5val, ohdr, cLen); ohdr['x-pw-noradle-cache'] = 'miss'; } } oraRes = ResultSetsFilter(oraRes, ohdr, flags); if ((compress = chooseZip(req)) && false && (ohdr['Content-Encoding'] === 'zip' || (ohdr['Content-Encoding'] === '?' && cLen > cfg.zip_threshold))) { oraRes = zipFilter(oraRes, ohdr, flags, {method : compress}); } else { delete ohdr['Content-Encoding']; } oraRes = MD5CalcFilter(oraRes, ohdr, flags); flags.headFixed && bakRes.writeHead(status, ohdr); if (cLen === 0) { fin(); } oraRes.on('data', function(data){ res.write(data); md5Content && md5Content.write(data); }); oraRes.on('end', function(chunks){ if (!flags.headFixed) { bakRes.writeHead(status, ohdr); } if (chunks) { chunks.forEach(function(chunk){ oraRes.emit('data', chunk); }); } if (md5Content) { md5Content.end(); md5Content = null; } step--; fin(); }); } function fin(){ if (step === 0) { res.end(); finished = true; } } }); } } ;