Version 11 of PHP under Tclhttpd

Updated 2004-09-29 05:38:39

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:

  • There's probably a better point to hook this into the cgi library, but at least this'll get someone started
  • If php-dir is outside the docroot (which, I think is a no-no for cgi), and is registered as a virtual directory, then PHP complains of "No Input File"

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... :-(


Another way to achieve the same thing would be to scatter some random delays in the Tcl code.


See also PHP and Tclhttpd

[ Category TclHttpd ]