package require html
namespace eval scgi {
proc listen {port} {
socket -server [namespace code connect] $port
}
proc connect {sock ip port} {
fconfigure $sock -blocking 0 -translation {binary crlf}
fileevent $sock readable [namespace code [list read_length $sock {}]]
}
proc read_length {sock data} {
append data [read $sock]
if {[eof $sock]} {
close $sock
return
}
set colonIdx [string first : $data]
if {$colonIdx == -1} {
# we don't have the headers length yet
fileevent $sock readable [namespace code [list read_length $sock $data]]
return
} else {
set length [string range $data 0 $colonIdx-1]
set data [string range $data $colonIdx+1 end]
read_headers $sock $length $data
}
}
proc read_headers {sock length data} {
append data [read $sock]
if {[string length $data] < $length+1} {
# we don't have the complete headers yet, wait for more
fileevent $sock readable [namespace code [list read_headers $sock $length $data]]
return
} else {
set headers [string range $data 0 $length-1]
set headers [lrange [split $headers \0] 0 end-1]
set body [string range $data $length+1 end]
set content_length [dict get $headers CONTENT_LENGTH]
read_body $sock $headers $content_length $body
}
}
proc read_body {sock headers content_length body} {
append body [read $sock]
if {[string length $body] < $content_length} {
# we don't have the complete body yet, wait for more
fileevent $sock readable [namespace code [list read_body $sock $headers $content_length $body]]
return
} else {
handle_request $sock $headers $body
}
}
}
proc handle_request {sock headers body} {
array set Headers $headers
parray Headers
puts $sock "Status: 200 OK"
puts $sock "Content-Type: text/html"
puts $sock ""
puts $sock "<HTML>"
puts $sock "<BODY>"
puts $sock [::html::tableFromArray Headers]
puts $sock "</BODY>"
puts $sock "<H3>Body</H3>"
puts $sock "<PRE>$body</PRE>"
if {$Headers(REQUEST_METHOD) eq "GET"} {
puts $sock {<FORM METHOD="post" ACTION="/scgi">}
foreach pair [split $Headers(QUERY_STRING) &] {
lassign [split $pair =] key val
puts $sock "$key: [::html::textInput $key $val]<BR>"
}
puts $sock "<BR>"
puts $sock {<INPUT TYPE="submit" VALUE="Try POST">}
} else {
puts $sock {<FORM METHOD="get" ACTION="/scgi">}
foreach pair [split $body &] {
lassign [split $pair =] key val
puts $sock "$key: [::html::textInput $key $val]<BR>"
}
puts $sock "<BR>"
puts $sock {<INPUT TYPE="submit" VALUE="Try GET">}
}
puts $sock "</FORM>"
puts $sock "</HTML>"
close $sock
}
scgi::listen 9999
vwait foreverWoof! uses a descendant of the above code for its SCGI support. Note the above code does not protect against malformed (and malicious) protocol input. Will update here once I fix Woof.MJ - Usually a webserver forms the SCGI requests and I think it's a fair assumption that those requests are valid. But because it has been a while since I looked at this, what would malicious protocol input be?APN I overlooked that the requests come from your own webserver so you are right. I missed that. By malicious, I meant input that would cause DoS attacks, e.g. sending a header length greater than the actual data would cause the above code to spike to 100% CPU, I think easily fixed by a EOF check. Thanks for this code BTW, as it is likely to be Woof!'s preferred web server interface mechanism as it supports Apache (with mod_scgi), nginx (mod_scgi), lighttpd (built-in) and IIS (with isapi_scgi).MJ - The request length is also determined by the server, so if that forms the requests correctly, that's not really a problem either. Of course an [eof] check never hurts. Also you are very welcome, I am glad this is useful to someone. For me it was just a nice small project.MS - I do wonder what webservers do with a POST request having a form variable CONTENT_LENGTH=987654321. IIUC, the SCGI protocol [2] forbids form variables named CONTENT_LENGTH and SCGI, as it forbids duplicate header names and those two are obligatory. Also REQUEST_METHOD and REQUEST_URI are likely to get you in trouble; any others? APN Form variables are not sent as HTTP headers. They are part of the content. The SCGI restriction refers to HTTP headers only.
APN In case any one's interested, I've implemented a SCGI ISAPI extension for IIS 5.1 and 6.0 (not sure yet if ISAPI extensions are compatible with IIS 7). It is available at [3]. Performance seems reasonable, trundling along at over 800/s (as measured by Apache ab) using the above script as the SCGI server on a middle-of-the-road dual core system running XP. Documentation is at [4].
