NB On several occasions, I've had the need to run/use some useful php (no-flames..) apps/scripts, but did not want to install a full-blown Apache distro...., much prefering to use tclhttpd;-) With these hacks below, PHP seems to work as a cgi under TclHttpd.
Edit cgi.tcl in the lib directory.., specifically CgiSpawn and CgiClose, essentially setting/unsetting env(REDIRECT_STATUS). This is needed so PHP does not complain about security breach and stop execution.
CMcC 20040805 - this bit below is troubling and probably not necessary. If you [lappend Cgi(env-pass) REDIRECT_STATUS] and set REDIRECT_STATUS to 1 before you start tclhttpd server process, then your variable will be passed to the cgi. Could you please test if that works, and if so, remove this code below? If you need to, you could set/unset ::env(REDIRECT_STATUS) within the mime type scripts.
I've put a suggested implementation below the original code ... someone try it out please?
proc CgiSpawn {sock script} { upvar #0 Httpd$sock data global env Cgi ##Nikos:This env var is needed to run php cgi securely, in directories under docroot set env(REDIRECT_STATUS) 1 if {$Cgi(cgi) >= $Cgi(maxcgi)} { Httpd_Error $sock 504 "Too many CGI's" return } incr Cgi(cgi) # for GET queries, pass the query as an argument to the cgi script if {$data(proto) == "POST"} { set arglist "" } else { set arglist $data(query) } set pwd [pwd] cd [file dirname $script] if {[catch {CgiExec $script $arglist} fd]} { cd $pwd Httpd_Error $sock 400 $fd incr Cgi(cgi) -1 return } cd $pwd Count cgihits set data(infile) $fd ;# So close happens in Httpd_SockClose set data(header) 0 ;# Have not read header yet set data(headerlist) {} ;# list of read headers set data(headercode) "200 data follows" ;# normal return fconfigure $fd -blocking 0 # Set up a timer in case it hangs catch {after cancel $data(cancel)} set data(cancel) [after $Cgi(timeout) CgiCancel $fd $sock] if {$data(proto) == "POST"} { fconfigure $fd -translation binary if {$data(count) == 0} { # Either there was no POST data, or we are inside a domain # that automatically read the POST data into data(query) already # Errors appear here because of the non-blocking writes. catch { puts -nonewline $fd $data(query) flush $fd } } else { # Pump the query data to the CGI process in the background # We set up fileevents after this finishes, so return now # fcopy bug in Tcl 8.3.2 and Tcl 8.4a2 and all previous versions # prevents this from working reliably on large amounts of POST data # fcopy $sock $fd -command [list CgiCopyDone $sock $fd] -size $data(count) fileevent $sock readable [list CgiCopyPost $sock $fd] return } } CgiCopyDone $sock $fd $data(count) "" }
proc CgiClose {fd sock {bytes {}} {error {}}} { global Cgi upvar #0 Httpd$sock data catch {after cancel $data(cancel)} incr Cgi(cgi) -1 ##Nikos:This env var was needed to run php cgi securely, now we unset it.. catch {unset ::env(REDIRECT_STATUS)} if {![info exists data(header)]} { Httpd_Error $sock 204 } else { Httpd_SockClose $sock 1 } if {[string length $error] > 0} { Log $sock CgiClose $error } }
glennj: you unser your env.var whether or not data(header) exists, so instead of doing it in 3 places, why don't you pull it out of the if blocks and place it after your incr call?
NB Done...
Add this to the custom directory
###Nikos:Hack to add php cgi capabilities into tclhttpd... ##Add the mime types Mtype_Add application/x-php .php Mtype_Add application/x-php .php3 Mtype_Add application/x-php .phtml ###Edit this your path, Win used to need full path; now it seems not to #set Cgi(php) "c:/php/php.exe" ;# For .php set Cgi(php) "php" ;# For .php proc Doc_application/x-php {path suffix sock} { upvar #0 Httpd$sock data Url_Handle [list CgiHandle $data(url) {} $path] $sock } proc Doc_application/x-php {path suffix sock} { upvar #0 Httpd$sock data Url_Handle [list CgiHandle $data(url) {} $path] $sock } puts "PHP CGI Stuff loaded" puts {You need to register your php cgi directories with Cgi_Directory}
Caveats:
For Windows only: cgi.tcl line 372 (inside the switch):
.php { return [open "|[list $Cgi(php) $script] $arglist" r+] }
CMcC here's what I was suggesting - put this in custom, and don't modify cgi.tcl, does this give the desired results? If it does, please remove the above ... unnecessary modification of cgi.tcl gives me dyspepsia.
##Add the mime types Mtype_Add application/x-php .php Mtype_Add application/x-php .php3 Mtype_Add application/x-php .phtml ###Edit this your path, Win used to need full path; now it seems not to #set Cgi(php) "c:/php/php.exe" ;# For .php set Cgi(php) "php" ;# For .php lappend Cgi(env-pass) REDIRECT_STATUS proc Doc_application/x-php {path suffix sock} { upvar #0 Httpd$sock data set ::env(REDIRECT_STATUS) 1 Url_Handle [list CgiHandle $data(url) {} $path] $sock unset ::env(REDIRECT_STATUS) } proc Doc_application/x-php {path suffix sock} { upvar #0 Httpd$sock data set ::env(REDIRECT_STATUS) 1 Url_Handle [list CgiHandle $data(url) {} $path] $sock unset ::env(REDIRECT_STATUS) } puts "PHP CGI Stuff loaded" puts {You need to register your php cgi directories with Cgi_Directory}
LES on 20040929: it does not work for me on Windows 98. I click my index.php file and only get its content in plain text. Will try Linux later... :-(
Les, did the original posted version work for you? I wonder whether that bit about registering your php directory as a Cgi directory might make a difference. -- CMcC
LES After a Tcl Chatroom session, CMcC came up with the solution. So the two code snippets above make PHP work with Tclhttpd.
CMcC it exposes a problem with Tclhttpd under Windows, though, which is that cgi processing has hard-coded mapping from extension to interpreter. Really, it should be more flexible. I'll submit an RFE so we remember that something could be done, but I'm not going to do the work, because I don't use Windows and hardly ever use CGI. MHo: This is indeed a big problem: most of the available perl-CGI-scripts found on the internet have the extension .CGI (not .PL) which, by default, is interpreted as a TCL-script in tclhttpd. And, if one change this behaviour in cgi.tcl (simple enough), existing tcl-scripts with .CGI-extension won't work anymore unless renamed....
schlenk On Windows the following worked:
Setting up PHP:
Setting up Tclhttp:
##Add the mime types Mtype_Add php application/x-php Mtype_Add php3 application/x-php Mtype_Add phtml application/x-php ###Edit this your path, Win used to need full path; now it seems not to set Cgi(php) "c:/php/php-cgi.exe" ;# For .php #set Cgi(php) "php" ;# For .php lappend Cgi(env-pass) REDIRECT_STATUS proc Doc_application/x-php {path suffix sock} { upvar #0 Httpd$sock data set ::env(REDIRECT_STATUS) 1 Url_Handle [list CgiHandle $data(url) {} $path] $sock unset ::env(REDIRECT_STATUS) } puts "PHP CGI Support loaded"
<?php echo("Hello World"); ?>
zbang On *nix, in addition to the custom code above, I had to modify lib/tclhttpd3.5.1/cgi.tcl so that it would actually use the Cgi array.
proc CgiExec {script arglist} { global tcl_platform global Cgi set prog "" set extn [string trim [file extension $script] "."] if { $extn != "cgi" } { if { [info exists Cgi($extn)] } { set prog "$Cgi($extn) " } } switch -- $tcl_platform(platform) { unix { if {[file exists /dev/stdout]} { # This trick "2> /dev/stdout" fails if you start the process in # the background under an xterm, then quit the xterm. In that case # the open raises an error. if { ! [catch {open "|$prog[list $script] $arglist 2> /dev/stdout" r+} pipe]} { return $pipe } } # We use cat to merge the stderr and stdout return [open "|$prog[list $script] $arglist |& cat" r+] }
I didn't try this on windows, but it might be more correct to change the windows section to use the $prog variable, too.
A couple of other things:
If you register the directory containing the php scripts as a cgi dir with Cgi_Directory, the Doc_application/x-php proc won't be called at all, as Doc_$mtype is a feature of the Doc domain handler, not the Cgi domain handler.
It's not clear whether the file extensions as registered with Mtype_Add should have a leading period. It looks like they should, based on what's in the MimeType array. There is code in a number of places to remove that period if present.
zbang One last thing: there are some things that php just won't do when run from a unix command line (as this setup does). For one thing, some of the global variables (i.e. $_SERVER'PHP_SELF') won't be set. You can see these by making a page of:
<?php phpinfo(); ?>
I've also seen problems in the parsing of query string values into php variables. I wasn't able to work around those in the time available. (Sometime, I might make a php module for the tcl web server. In my copious free time.)
Another way to achieve the same thing would be to scatter some random delays in the Tcl code.