├── .gitignore ├── README.md ├── nodejs-php-cgi.js ├── route.php ├── test_app.js └── webroot ├── index.php ├── pi.php └── post.php /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nodejs-php-cgi 2 | 3 | ``` 4 | pm2 => app.js*n => php-cgi-pool*n => php-router-controller(n*m) 5 | ``` 6 | 7 | # TODO 8 | 9 | * add routing/plugin support 10 | * pooling+auto-release(daily) / threshold-control / auto-release(weekly) 11 | * performance improve 12 | * code clean up 13 | * pm2 scripts 14 | * php-swoole/fpm/nginx integration for enterprise 15 | 16 | # reference 17 | 18 | * http://blog.milkfarmsoft.com/2006/06/fastcgi-in-php-the-way-it-could-be/ 19 | * https://github.com/sihorton/node-php-cgi/blob/master/index.js 20 | * https://github.com/hushicai/node-phpcgi 21 | * https://github.com/fgnass/gateway 22 | * https://github.com/thomasdondorf/phantomjs-pool 23 | -------------------------------------------------------------------------------- /nodejs-php-cgi.js: -------------------------------------------------------------------------------- 1 | const os = require("os"); 2 | const spawn = require('child_process').spawn; 3 | const path = require("path"); 4 | 5 | module.exports = function(opts){ 6 | 7 | var logger=opts.logger || console; 8 | 9 | return function(req,res){ 10 | var tm0=new Date(); 11 | 12 | var url=req.url; 13 | var host = (req.headers.host || '').split(':') 14 | var _env={ 15 | __proto__: opts.env || {} //extends from.. 16 | ,SCRIPT_FILENAME:__dirname + path.sep + "route.php" 17 | ,REMOTE_ADDR: req.connection.remoteAddress //TODO x-forwarded-for, x-real-ip 18 | //,'PATH_INFO':'/Users/wanjochan/Downloads/github/nodejs-php-cgi/test.php' 19 | //,'SERVER_SOFTWARE':"nodejs" 20 | ,SERVER_PROTOCOL:"HTTP/1.1" 21 | ,GATEWAY_INTERFACE:"CGI/1.1" 22 | ,REQUEST_TIME_0:tm0.getTime()/1000 23 | ,REQUEST_METHOD:req.method 24 | //,"PATH_INFO":path.normalize(reqEnv['DOCUMENT_ROOT']+reqdata.pathname) 25 | ,PATH_INFO:url 26 | ,QUERY_STRING: url.query || '' 27 | ,SERVER_NAME: host[0] 28 | ,SERVER_PORT: host[1] || 80 29 | ,HTTPS: req.connection.encrypted ? 'On' : 'Off' 30 | //,REQUEST_URI:req.path 31 | //,REDIRECT_STATUS_ENV,0 32 | ,REDIRECT_STATUS:200//This PHP CGI binary was compiled with force-cgi-redirect enabled. This means that a page will only be served up if the REDIRECT_STATUS CGI variable is set, e.g. via an Apache Action directive... 33 | }; 34 | //other headers 35 | for (var theHeader in req.headers) { 36 | _env['HTTP_' + theHeader.toUpperCase().split("-").join("_")] = req.headers[theHeader]; 37 | } 38 | //logger.log(_env); 39 | var cgi = spawn(opts.bin || '/usr/local/bin/php-cgi', 40 | opts.binarga || [], 41 | {'env':_env 42 | //,detached: true//https://nodejs.org/api/child_process.html#child_process_options_detached 43 | //On Windows, setting options.detached to true makes it possible for the child process to continue running after the parent exits. The child will have its own console window. Once enabled for a child process, it cannot be disabled. 44 | //On non-Windows platforms, if options.detached is set to true, the child process will be made the leader of a new process group and session. Note that child processes may continue running after the parent exits regardless of whether they are detached or not. See setsid(2) for more information. 45 | }).on('error',function(data){ 46 | logger.log("cgi error:"+data.toString());//TODO use a logger to do something 47 | }); 48 | cgi.stderr.on('data',function(data) { 49 | logger.log("stderr.on(data):"+data.toString()); 50 | }); 51 | var buffer=[]; 52 | res.on('error',function(ex){ 53 | logger.log("res.on(error):"+ex); 54 | logger.log(buffer); 55 | }); 56 | var headersSent = false; 57 | cgi.stdout.on('end',function(){ 58 | res.end(); 59 | }).on('data',function(data){ 60 | //logger.log(data.toString()); 61 | buffer.push(data.toString()); 62 | if (headersSent) { 63 | res.write(data); 64 | } else { 65 | var data_s=data.toString(); 66 | var lines = data_s.split("\r\n");//TODO may improve the speed later 67 | var hhh=[]; 68 | for(var l=0;lfunction(&$uu,$pattern,$matches){ 14 | $_c=$_REQUEST['_c']=$_GET['_c']=$matches[2]; 15 | $_m=$_REQUEST['_m']=$_GET['_m']=$matches[3]; 16 | $uu=dirname($uu).'/'.($matches[5]=='static'?'static':'index').'.php'; 17 | }, 18 | 19 | "/([^\/]*)\.shtml$/"=>function(&$uu,$pattern,$matches){ 20 | $_REQUEST['_p']=$_GET['_p']=$matches[1]; 21 | if(file_exists(dirname($uu).'/shtml.php')){ 22 | chdir(dirname($uu)); 23 | require dirname($uu).'/shtml.php'; 24 | return true; 25 | } 26 | }, 27 | "/([^\/]*)\.api$/"=>function(&$uu,$pattern,$matches){ 28 | $_REQUEST['_m']=$_GET['_m']=$matches[1]; 29 | $uu=dirname($uu).'/index.php'; 30 | }, 31 | "/([^\/]*)\.static$/"=>function(&$uu,$pattern,$matches){ 32 | $_REQUEST['_m']=$_GET['_m']=$matches[1]; 33 | $uu=dirname($uu).'/static.php'; 34 | }, 35 | //TODO ./upload/ mapping to .. 36 | 37 | "/\/$/"=>function($uu){ 38 | if(file_exists($uu .'index.php')){ 39 | chdir($uu); 40 | require $uu.'index.php'; 41 | return true; 42 | } 43 | }, 44 | "/\.php$/"=>function($uu,$pattern){ 45 | if(file_exists($uu)){ 46 | chdir(dirname($uu)); 47 | require basename($uu); 48 | return true; 49 | }else{ 50 | echo "404 $uu ...";return true; 51 | } 52 | }, 53 | "/\.(js|css|jpg|jpeg|png|gif|ttf|htm|json)$/"=>function($uu,$pattern){ 54 | if(file_exists($uu)){ 55 | echo file_get_contents($uu); 56 | return true; 57 | } 58 | }, 59 | ''=>function($uu){ 60 | print "404 $uu"; 61 | } 62 | ) as $k=>$v){ 63 | $matches=array(); 64 | if($k=='' || preg_match($k,$uu,$matches)){ 65 | if(true===$v($uu,$k,$matches)) break; else continue; 66 | } 67 | } 68 | })( 69 | 'webroot/', 70 | (function(){ 71 | $rt=$_SERVER['REQUEST_URI']; 72 | if($rt) return ltrim($rt); 73 | $rt=$_SERVER['PATH_INFO']; 74 | if($rt) return ltrim($rt); 75 | return '/';//nothing? 76 | })(), 77 | (function(){ 78 | global $HTTP_RAW_POST_DATA,$_POST; 79 | $POST_TRY=0; 80 | if($HTTP_RAW_POST_DATA==""){ 81 | $POST_TRY=1; 82 | $HTTP_RAW_POST_DATA=file_get_contents("php://stdin"); 83 | if($HTTP_RAW_POST_DATA==""){ 84 | $POST_TRY=2; 85 | $HTTP_RAW_POST_DATA=file_get_contents("php://input"); 86 | if($HTTP_RAW_POST_DATA==""){ 87 | $POST_TRY=3; 88 | $HTTP_RAW_POST_DATA=json_encode($_POST);//join('&',$_POST); 89 | } 90 | } 91 | } 92 | return array($HTTP_RAW_POST_DATA,$POST_TRY); 93 | })() 94 | ); 95 | 96 | -------------------------------------------------------------------------------- /test_app.js: -------------------------------------------------------------------------------- 1 | //eg: 2 | //node test_app.js [-port=1338] [-cgi=`which php-cgi`] 3 | function argv2o(argv){ 4 | var argv_o={}; 5 | for(k in argv){ 6 | var m,mm,v=argv[k]; 7 | argv_o[""+k]=v; 8 | (m=v.match(/^--?([a-zA-Z0-9-_]*)=(.*)/))&&(argv_o[m[1]]=(mm=m[2].match(/^".*"$/))?mm[1]:m[2]); 9 | } 10 | return argv_o; 11 | } 12 | var argo=argv2o(process.argv);//console.log(argo) 13 | const os=require("os"); 14 | if(!argo.cgi)argo.cgi=argo.bin || (""+require('child_process').execSync((os.EOL=="\r\n"?"cmd /k where":"which")+" php-cgi")).split(os.EOL)[0].toString(); 15 | console.log(argo); 16 | var http_server=require('http').createServer(require("./nodejs-php-cgi.js")({ bin:argo.cgi })) 17 | .listen(ppp=argo.port||80,hhh=argo.host||'0.0.0.0',()=>{ 18 | console.log(hhh+':'+ppp); 19 | }); 20 | -------------------------------------------------------------------------------- /webroot/index.php: -------------------------------------------------------------------------------- 1 | 7 |
8 | 9 | 10 |
11 |