PageRenderTime 39ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/PuttyKnife/dist/sdx-new.vfs/lib/app-sdx/httpd.tcl

http://puttyknife.googlecode.com/
TCL | 350 lines | 264 code | 50 blank | 36 comment | 42 complexity | e60e45e6a8557d3db495a26a71175f0c MD5 | raw file
Possible License(s): GPL-3.0
  1. # Simple Sample httpd/1.0 server in 250 lines of Tcl
  2. # Stephen Uhler / Brent Welch (c) 1996 Sun Microsystems
  3. # See the file "license.terms" for information on usage and redistribution
  4. # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  5. # This is a working sample httpd server written entirely in TCL with the
  6. # CGI and imagemap capability removed. It has been tested on the Mac, PC
  7. # and Unix. It is intended as sample of how to write internet servers in
  8. # Tcl. This sample server was derived from a full-featured httpd server,
  9. # also written entirely in Tcl.
  10. # Comments or questions welcome (stephen.uhler@sun.com)
  11. # Httpd is a global array containing the global server state
  12. # root: the root of the document directory
  13. # port: The port this server is serving
  14. # listen: the main listening socket id
  15. # accepts: a count of accepted connections so far
  16. array set Httpd {
  17. -version "Tcl Httpd-Lite 1.0"
  18. -launch 0
  19. -port 8080
  20. -ipaddr ""
  21. -default index.html
  22. -root /wwwroot
  23. -bufsize 32768
  24. -sockblock 0
  25. -config ""
  26. }
  27. set Httpd(-host) [info hostname]
  28. # HTTP/1.0 error codes (the ones we use)
  29. array set HttpdErrors {
  30. 204 {No Content}
  31. 400 {Bad Request}
  32. 404 {Not Found}
  33. 503 {Service Unavailable}
  34. 504 {Service Temporarily Unavailable}
  35. }
  36. # Start the server by listening for connections on the desired port.
  37. proc Httpd_Server {args} {
  38. global Httpd
  39. if {[llength $args] == 1} {
  40. set args [lindex $args 0]
  41. }
  42. array set Httpd $args
  43. if {![file isdirectory $Httpd(-root)]} {
  44. return -code error "Bad root directory \"$Httpd(-root)\""
  45. }
  46. if {![file exists [file join $Httpd(-root) $Httpd(-default)]]} {
  47. # Try and find a good default
  48. foreach idx {index.htm index.html default.htm contents.htm} {
  49. if {[file exists [file join $Httpd(-root) $idx]]} {
  50. set Httpd(-default) $idx
  51. break
  52. }
  53. }
  54. }
  55. if {![file exists [file join $Httpd(-root) $Httpd(-default)]]} {
  56. return -code error "Bad index page \"$Httpd(-default)\""
  57. }
  58. if {$Httpd(-ipaddr) != ""} {
  59. set Httpd(listen) [socket -server HttpdAccept \
  60. -myaddr $Httpd(-ipaddr) $Httpd(-port)]
  61. } else {
  62. set Httpd(listen) [socket -server HttpdAccept $Httpd(-port)]
  63. }
  64. set Httpd(accepts) 0
  65. if {$Httpd(-port) == 0} {
  66. set Httpd(-port) [lindex [fconfigure $Httpd(listen) -sockname] 2]
  67. }
  68. return $Httpd(-port)
  69. }
  70. # Accept a new connection from the server and set up a handler
  71. # to read the request from the client.
  72. proc HttpdAccept {newsock ipaddr port} {
  73. global Httpd
  74. upvar #0 Httpd$newsock data
  75. incr Httpd(accepts)
  76. fconfigure $newsock -blocking $Httpd(-sockblock) \
  77. -buffersize $Httpd(-bufsize) \
  78. -translation {auto crlf}
  79. Httpd_Log $newsock Connect $ipaddr $port
  80. set data(ipaddr) $ipaddr
  81. fileevent $newsock readable [list HttpdRead $newsock]
  82. }
  83. # read data from a client request
  84. proc HttpdRead { sock } {
  85. upvar #0 Httpd$sock data
  86. set readCount [gets $sock line]
  87. if {![info exists data(state)]} {
  88. if [regexp {(POST|GET) ([^?]+)\??([^ ]*) HTTP/1.[01]} \
  89. $line x data(proto) data(url) data(query)] {
  90. set data(state) mime
  91. Httpd_Log $sock Query $line
  92. } else {
  93. HttpdError $sock 400
  94. Httpd_Log $sock Error "bad first line:$line"
  95. HttpdSockDone $sock
  96. }
  97. return
  98. }
  99. # string compare $readCount 0 maps -1 to -1, 0 to 0, and > 0 to 1
  100. set state [string compare $readCount 0],$data(state),$data(proto)
  101. switch -- $state {
  102. 0,mime,GET -
  103. 0,query,POST { HttpdRespond $sock }
  104. 0,mime,POST { set data(state) query }
  105. 1,mime,POST -
  106. 1,mime,GET {
  107. if [regexp {([^:]+):[ ]*(.*)} $line dummy key value] {
  108. set data(mime,[string tolower $key]) $value
  109. }
  110. }
  111. 1,query,POST {
  112. set data(query) $line
  113. HttpdRespond $sock
  114. }
  115. default {
  116. if [eof $sock] {
  117. Httpd_Log $sock Error "unexpected eof on <$data(url)> request"
  118. } else {
  119. Httpd_Log $sock Error "unhandled state <$state> fetching <$data(url)>"
  120. }
  121. HttpdError $sock 404
  122. HttpdSockDone $sock
  123. }
  124. }
  125. }
  126. proc HttpdCopyDone { in sock bytes {error ""}} {
  127. #tclLog "CopyDone $sock $bytes $error"
  128. catch {close $in}
  129. HttpdSockDone $sock
  130. }
  131. # Close a socket.
  132. # We'll use this to implement keep-alives some day.
  133. proc HttpdSockDone { sock } {
  134. upvar #0 Httpd$sock data
  135. unset data
  136. close $sock
  137. }
  138. # Respond to the query.
  139. proc HttpdRespond { sock } {
  140. global Httpd
  141. upvar #0 Httpd$sock data
  142. set mypath [HttpdUrl2File $Httpd(-root) $data(url)]
  143. if {[string length $mypath] == 0} {
  144. HttpdError $sock 400
  145. Httpd_Log $sock Error "$data(url) invalid path"
  146. HttpdSockDone $sock
  147. return
  148. }
  149. if {![catch {open $mypath} in]} {
  150. puts $sock "HTTP/1.0 200 Data follows"
  151. puts $sock "Date: [HttpdDate [clock seconds]]"
  152. puts $sock "Server: $Httpd(-version)"
  153. puts $sock "Last-Modified: [HttpdDate [file mtime $mypath]]"
  154. puts $sock "Content-Type: [HttpdContentType $mypath]"
  155. puts $sock "Content-Length: [file size $mypath]"
  156. puts $sock ""
  157. fconfigure $sock -translation binary -blocking $Httpd(-sockblock)
  158. fconfigure $in -translation binary -blocking 0
  159. flush $sock
  160. fileevent $sock readable {}
  161. fcopy $in $sock -command [list HttpdCopyDone $in $sock]
  162. #HttpdSockDone $sock
  163. } else {
  164. HttpdError $sock 404
  165. Httpd_Log $sock Error "$data(url) $in"
  166. HttpdSockDone $sock
  167. }
  168. }
  169. # convert the file suffix into a mime type
  170. # add your own types as needed
  171. array set HttpdMimeType {
  172. {} text/plain
  173. .txt text/plain
  174. .htm text/html
  175. .html text/html
  176. .gif image/gif
  177. .jpg image/jpeg
  178. .xbm image/x-xbitmap
  179. }
  180. proc HttpdContentType {path} {
  181. global HttpdMimeType
  182. set type text/plain
  183. catch {set type $HttpdMimeType([string tolower [file extension $path]])}
  184. return $type
  185. }
  186. # Generic error response.
  187. set HttpdErrorFormat {
  188. <title>Error: %1$s</title>
  189. Got the error: <b>%2$s</b><br>
  190. while trying to obtain <b>%3$s</b>
  191. }
  192. proc HttpdError {sock code} {
  193. upvar #0 Httpd$sock data
  194. global HttpdErrors HttpdErrorFormat Httpd
  195. append data(url) ""
  196. set message [format $HttpdErrorFormat $code $HttpdErrors($code) $data(url)]
  197. puts $sock "HTTP/1.0 $code $HttpdErrors($code)"
  198. puts $sock "Date: [HttpdDate [clock seconds]]"
  199. puts $sock "Server: $Httpd(-version)"
  200. puts $sock "Content-Length: [string length $message]"
  201. puts $sock ""
  202. puts -nonewline $sock $message
  203. }
  204. # Generate a date string in HTTP format.
  205. proc HttpdDate {clicks} {
  206. return [clock format $clicks -format {%a, %d %b %Y %T %Z}]
  207. }
  208. # Log an Httpd transaction.
  209. # This should be replaced as needed.
  210. proc Httpd_Log {sock reason args} {
  211. global httpdLog httpClicks
  212. if {[info exists httpdLog]} {
  213. if ![info exists httpClicks] {
  214. set last 0
  215. } else {
  216. set last $httpClicks
  217. }
  218. set httpClicks [clock seconds]
  219. set ts [clock format [clock seconds] -format {%Y%m%d %T}]
  220. puts $httpdLog "$ts ([expr $httpClicks - $last])\t$sock\t$reason\t[join $args { }]"
  221. }
  222. }
  223. # Convert a url into a pathname.
  224. # This is probably not right.
  225. proc HttpdUrl2File {root url} {
  226. global HttpdUrlCache Httpd
  227. if {![info exists HttpdUrlCache($url)]} {
  228. lappend pathlist $root
  229. set level 0
  230. foreach part [split $url /] {
  231. set part [HttpdCgiMap $part]
  232. if [regexp {[:/]} $part] {
  233. return [set HttpdUrlCache($url) ""]
  234. }
  235. switch -- $part {
  236. . { }
  237. .. {incr level -1}
  238. default {incr level}
  239. }
  240. if {$level <= 0} {
  241. return [set HttpdUrlCache($url) ""]
  242. }
  243. lappend pathlist $part
  244. }
  245. set file [eval file join $pathlist]
  246. if {[file isdirectory $file]} {
  247. set file [file join $file $Httpd(-default)]
  248. }
  249. set HttpdUrlCache($url) $file
  250. }
  251. return $HttpdUrlCache($url)
  252. }
  253. # Decode url-encoded strings.
  254. proc HttpdCgiMap {data} {
  255. regsub -all {([][$\\])} $data {\\\1} data
  256. regsub -all {%([0-9a-fA-F][0-9a-fA-F])} $data {[format %c 0x\1]} data
  257. return [subst $data]
  258. }
  259. proc bgerror {msg} {
  260. global errorInfo
  261. puts stderr "bgerror: $errorInfo"
  262. }
  263. proc openurl url {
  264. global tcl_platform
  265. if {[lindex $tcl_platform(os) 1] == "NT"} {
  266. exec cmd /c start $url &
  267. } else {
  268. exec start $url &
  269. }
  270. }
  271. set httpdLog stderr
  272. upvar #0 Httpd opts
  273. while {[llength $argv] > 0} {
  274. set option [lindex $argv 0]
  275. if {![info exists opts($option)] || [llength $argv] == 1} {
  276. puts stderr "usage: httpd ?options?"
  277. puts stderr "\nwhere options are any of the following:\n"
  278. foreach opt [lsort [array names opts -*]] {
  279. puts stderr [format "\t%-15s default: %s" $opt $opts($opt)]
  280. }
  281. exit 1
  282. }
  283. set opts($option) [lindex $argv 1]
  284. set argv [lrange $argv 2 end]
  285. }
  286. catch {
  287. package require vfs
  288. vfs::auto $opts(-root) -readonly
  289. }
  290. if {$opts(-config) != ""} {
  291. source $opts(-config)
  292. }
  293. Httpd_Server [array get opts]
  294. puts stderr "Accepting connections on http://$Httpd(-host):$Httpd(-port)/"
  295. if {$Httpd(-launch)} {
  296. openurl "http://$Httpd(-host):$Httpd(-port)/"
  297. }
  298. if {![info exists tcl_service]} {
  299. vwait forever ;# start the Tcl event loop
  300. }