PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/v2/testdata/_tcl8.6.8/pkgs/thread2.8.2/tcl/phttpd/phttpd.tcl

https://gitlab.com/zchee/ccgo
TCL | 686 lines | 294 code | 84 blank | 308 comment | 36 complexity | 9e30fe2563f34eb23bcda900ad2d0f0d MD5 | raw file
  1. #
  2. # phttpd.tcl --
  3. #
  4. # Simple Sample httpd/1.0 server in 250 lines of Tcl.
  5. # Stephen Uhler / Brent Welch (c) 1996 Sun Microsystems.
  6. #
  7. # Modified to use namespaces, direct url-to-procedure access
  8. # and thread pool package. Grown little larger since ;)
  9. #
  10. # Usage:
  11. # phttpd::create port
  12. #
  13. # port Tcp port where the server listens
  14. #
  15. # Example:
  16. #
  17. # # tclsh8.4
  18. # % source phttpd.tcl
  19. # % phttpd::create 5000
  20. # % vwait forever
  21. #
  22. # Starts the server on the port 5000. Also, look at the Httpd array
  23. # definition in the "phttpd" namespace declaration to find out
  24. # about other options you may put on the command line.
  25. #
  26. # You can use: http://localhost:5000/monitor URL to test the
  27. # server functionality.
  28. #
  29. # Copyright (c) 2002 by Zoran Vasiljevic.
  30. #
  31. # See the file "license.terms" for information on usage and
  32. # redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  33. # -----------------------------------------------------------------------------
  34. package require Tcl 8.4
  35. package require Thread 2.5
  36. #
  37. # Modify the following in order to load the
  38. # example Tcl implementation of threadpools.
  39. # Per default, the C-level threadpool is used.
  40. #
  41. if {0} {
  42. eval [set TCL_TPOOL {source ../tpool/tpool.tcl}]
  43. }
  44. namespace eval phttpd {
  45. variable Httpd; # Internal server state and config params
  46. variable MimeTypes; # Cache of file-extension/mime-type
  47. variable HttpCodes; # Portion of well-known http return codes
  48. variable ErrorPage; # Format of error response page in html
  49. array set Httpd {
  50. -name phttpd
  51. -vers 1.0
  52. -root "."
  53. -index index.htm
  54. }
  55. array set HttpCodes {
  56. 400 "Bad Request"
  57. 401 "Not Authorized"
  58. 404 "Not Found"
  59. 500 "Server error"
  60. }
  61. array set MimeTypes {
  62. {} "text/plain"
  63. .txt "text/plain"
  64. .htm "text/html"
  65. .htm "text/html"
  66. .gif "image/gif"
  67. .jpg "image/jpeg"
  68. .png "image/png"
  69. }
  70. set ErrorPage {
  71. <title>Error: %1$s %2$s</title>
  72. <h1>%3$s</h1>
  73. <p>Problem in accessing "%4$s" on this server.</p>
  74. <hr>
  75. <i>%5$s/%6$s Server at %7$s Port %8$s</i>
  76. }
  77. }
  78. #
  79. # phttpd::create --
  80. #
  81. # Start the server by listening for connections on the desired port.
  82. #
  83. # Arguments:
  84. # port
  85. # args
  86. #
  87. # Side Effects:
  88. # None..
  89. #
  90. # Results:
  91. # None.
  92. #
  93. proc phttpd::create {port args} {
  94. variable Httpd
  95. set arglen [llength $args]
  96. if {$arglen} {
  97. if {$arglen % 2} {
  98. error "wrong \# args, should be: key1 val1 key2 val2..."
  99. }
  100. set opts [array names Httpd]
  101. foreach {arg val} $args {
  102. if {[lsearch $opts $arg] == -1} {
  103. error "unknown option \"$arg\""
  104. }
  105. set Httpd($arg) $val
  106. }
  107. }
  108. #
  109. # Create thread pool with max 8 worker threads.
  110. #
  111. if {[info exists ::TCL_TPOOL] == 0} {
  112. #
  113. # Using the internal C-based thread pool
  114. #
  115. set initcmd "source ../phttpd/phttpd.tcl"
  116. } else {
  117. #
  118. # Using the Tcl-level hand-crafted thread pool
  119. #
  120. append initcmd "source ../phttpd/phttpd.tcl" \n $::TCL_TPOOL
  121. }
  122. set Httpd(tpid) [tpool::create -maxworkers 8 -initcmd $initcmd]
  123. #
  124. # Start the server on the given port. Note that we wrap
  125. # the actual accept with a helper after/idle callback.
  126. # This is a workaround for a well-known Tcl bug.
  127. #
  128. socket -server [namespace current]::_Accept $port
  129. }
  130. #
  131. # phttpd::_Accept --
  132. #
  133. # Helper procedure to solve Tcl shared-channel bug when responding
  134. # to incoming connection and transfering the channel to other thread(s).
  135. #
  136. # Arguments:
  137. # sock incoming socket
  138. # ipaddr IP address of the remote peer
  139. # port Tcp port used for this connection
  140. #
  141. # Side Effects:
  142. # None.
  143. #
  144. # Results:
  145. # None.
  146. #
  147. proc phttpd::_Accept {sock ipaddr port} {
  148. after idle [list [namespace current]::Accept $sock $ipaddr $port]
  149. }
  150. #
  151. # phttpd::Accept --
  152. #
  153. # Accept a new connection from the client.
  154. #
  155. # Arguments:
  156. # sock
  157. # ipaddr
  158. # port
  159. #
  160. # Side Effects:
  161. # None..
  162. #
  163. # Results:
  164. # None.
  165. #
  166. proc phttpd::Accept {sock ipaddr port} {
  167. variable Httpd
  168. #
  169. # Setup the socket for sane operation
  170. #
  171. fconfigure $sock -blocking 0 -translation {auto crlf}
  172. #
  173. # Detach the socket from current interpreter/tnread.
  174. # One of the worker threads will attach it again.
  175. #
  176. thread::detach $sock
  177. #
  178. # Send the work ticket to threadpool.
  179. #
  180. tpool::post -detached $Httpd(tpid) [list [namespace current]::Ticket $sock]
  181. }
  182. #
  183. # phttpd::Ticket --
  184. #
  185. # Job ticket to run in the thread pool thread.
  186. #
  187. # Arguments:
  188. # sock
  189. #
  190. # Side Effects:
  191. # None..
  192. #
  193. # Results:
  194. # None.
  195. #
  196. proc phttpd::Ticket {sock} {
  197. thread::attach $sock
  198. fileevent $sock readable [list [namespace current]::Read $sock]
  199. #
  200. # End of processing is signalized here.
  201. # This will release the worker thread.
  202. #
  203. vwait [namespace current]::done
  204. }
  205. #
  206. # phttpd::Read --
  207. #
  208. # Read data from client and parse incoming http request.
  209. #
  210. # Arguments:
  211. # sock
  212. #
  213. # Side Effects:
  214. # None.
  215. #
  216. # Results:
  217. # None.
  218. #
  219. proc phttpd::Read {sock} {
  220. variable Httpd
  221. variable data
  222. set data(sock) $sock
  223. while {1} {
  224. if {[catch {gets $data(sock) line} readCount] || [eof $data(sock)]} {
  225. return [Done]
  226. }
  227. if {![info exists data(state)]} {
  228. set pat {(POST|GET) ([^?]+)\??([^ ]*) HTTP/1\.[0-9]}
  229. if {[regexp $pat $line x data(proto) data(url) data(query)]} {
  230. set data(state) mime
  231. continue
  232. } else {
  233. Log error "bad request line: (%s)" $line
  234. Error 400
  235. return [Done]
  236. }
  237. }
  238. # string compare $readCount 0 maps -1 to -1, 0 to 0, and > 0 to 1
  239. set state [string compare $readCount 0],$data(state),$data(proto)
  240. switch -- $state {
  241. "0,mime,GET" - "0,query,POST" {
  242. Respond
  243. return [Done]
  244. }
  245. "0,mime,POST" {
  246. set data(state) query
  247. set data(query) ""
  248. }
  249. "1,mime,POST" - "1,mime,GET" {
  250. if [regexp {([^:]+):[ ]*(.*)} $line dummy key value] {
  251. set data(mime,[string tolower $key]) $value
  252. }
  253. }
  254. "1,query,POST" {
  255. append data(query) $line
  256. set clen $data(mime,content-length)
  257. if {($clen - [string length $data(query)]) <= 0} {
  258. Respond
  259. return [Done]
  260. }
  261. }
  262. default {
  263. if [eof $data(sock)] {
  264. Log error "unexpected eof; client closed connection"
  265. return [Done]
  266. } else {
  267. Log error "bad http protocol state: %s" $state
  268. Error 400
  269. return [Done]
  270. }
  271. }
  272. }
  273. }
  274. }
  275. #
  276. # phttpd::Done --
  277. #
  278. # Close the connection socket
  279. #
  280. # Arguments:
  281. # s
  282. #
  283. # Side Effects:
  284. # None..
  285. #
  286. # Results:
  287. # None.
  288. #
  289. proc phttpd::Done {} {
  290. variable done
  291. variable data
  292. close $data(sock)
  293. if {[info exists data]} {
  294. unset data
  295. }
  296. set done 1 ; # Releases the request thread (See Ticket procedure)
  297. }
  298. #
  299. # phttpd::Respond --
  300. #
  301. # Respond to the query.
  302. #
  303. # Arguments:
  304. # s
  305. #
  306. # Side Effects:
  307. # None..
  308. #
  309. # Results:
  310. # None.
  311. #
  312. proc phttpd::Respond {} {
  313. variable data
  314. if {[info commands $data(url)] == $data(url)} {
  315. #
  316. # Service URL-procedure
  317. #
  318. if {[catch {
  319. puts $data(sock) "HTTP/1.0 200 OK"
  320. puts $data(sock) "Date: [Date]"
  321. puts $data(sock) "Last-Modified: [Date]"
  322. } err]} {
  323. Log error "client closed connection prematurely: %s" $err
  324. return
  325. }
  326. if {[catch {$data(url) data} err]} {
  327. Log error "%s: %s" $data(url) $err
  328. }
  329. } else {
  330. #
  331. # Service regular file path
  332. #
  333. set mypath [Url2File $data(url)]
  334. if {![catch {open $mypath} i]} {
  335. if {[catch {
  336. puts $data(sock) "HTTP/1.0 200 OK"
  337. puts $data(sock) "Date: [Date]"
  338. puts $data(sock) "Last-Modified: [Date [file mtime $mypath]]"
  339. puts $data(sock) "Content-Type: [ContentType $mypath]"
  340. puts $data(sock) "Content-Length: [file size $mypath]"
  341. puts $data(sock) ""
  342. fconfigure $data(sock) -translation binary -blocking 0
  343. fconfigure $i -translation binary
  344. fcopy $i $data(sock)
  345. close $i
  346. } err]} {
  347. Log error "client closed connection prematurely: %s" $err
  348. }
  349. } else {
  350. Log error "%s: %s" $data(url) $i
  351. Error 404
  352. }
  353. }
  354. }
  355. #
  356. # phttpd::ContentType --
  357. #
  358. # Convert the file suffix into a mime type.
  359. #
  360. # Arguments:
  361. # path
  362. #
  363. # Side Effects:
  364. # None..
  365. #
  366. # Results:
  367. # None.
  368. #
  369. proc phttpd::ContentType {path} {
  370. # @c Convert the file suffix into a mime type.
  371. variable MimeTypes
  372. set type "text/plain"
  373. catch {set type $MimeTypes([file extension $path])}
  374. return $type
  375. }
  376. #
  377. # phttpd::Error --
  378. #
  379. # Emit error page
  380. #
  381. # Arguments:
  382. # s
  383. # code
  384. #
  385. # Side Effects:
  386. # None..
  387. #
  388. # Results:
  389. # None.
  390. #
  391. proc phttpd::Error {code} {
  392. variable Httpd
  393. variable HttpCodes
  394. variable ErrorPage
  395. variable data
  396. append data(url) ""
  397. set msg \
  398. [format $ErrorPage \
  399. $code \
  400. $HttpCodes($code) \
  401. $HttpCodes($code) \
  402. $data(url) \
  403. $Httpd(-name) \
  404. $Httpd(-vers) \
  405. [info hostname] \
  406. 80 \
  407. ]
  408. if {[catch {
  409. puts $data(sock) "HTTP/1.0 $code $HttpCodes($code)"
  410. puts $data(sock) "Date: [Date]"
  411. puts $data(sock) "Content-Length: [string length $msg]"
  412. puts $data(sock) ""
  413. puts $data(sock) $msg
  414. } err]} {
  415. Log error "client closed connection prematurely: %s" $err
  416. }
  417. }
  418. #
  419. # phttpd::Date --
  420. #
  421. # Generate a date string in HTTP format.
  422. #
  423. # Arguments:
  424. # seconds
  425. #
  426. # Side Effects:
  427. # None..
  428. #
  429. # Results:
  430. # None.
  431. #
  432. proc phttpd::Date {{seconds 0}} {
  433. # @c Generate a date string in HTTP format.
  434. if {$seconds == 0} {
  435. set seconds [clock seconds]
  436. }
  437. clock format $seconds -format {%a, %d %b %Y %T %Z} -gmt 1
  438. }
  439. #
  440. # phttpd::Log --
  441. #
  442. # Log an httpd transaction.
  443. #
  444. # Arguments:
  445. # reason
  446. # format
  447. # args
  448. #
  449. # Side Effects:
  450. # None..
  451. #
  452. # Results:
  453. # None.
  454. #
  455. proc phttpd::Log {reason format args} {
  456. set messg [eval format [list $format] $args]
  457. set stamp [clock format [clock seconds] -format "%d/%b/%Y:%H:%M:%S"]
  458. puts stderr "\[$stamp\]\[-thread[thread::id]-\] $reason: $messg"
  459. }
  460. #
  461. # phttpd::Url2File --
  462. #
  463. # Convert a url into a pathname.
  464. #
  465. # Arguments:
  466. # url
  467. #
  468. # Side Effects:
  469. # None..
  470. #
  471. # Results:
  472. # None.
  473. #
  474. proc phttpd::Url2File {url} {
  475. variable Httpd
  476. lappend pathlist $Httpd(-root)
  477. set level 0
  478. foreach part [split $url /] {
  479. set part [CgiMap $part]
  480. if [regexp {[:/]} $part] {
  481. return ""
  482. }
  483. switch -- $part {
  484. "." { }
  485. ".." {incr level -1}
  486. default {incr level}
  487. }
  488. if {$level <= 0} {
  489. return ""
  490. }
  491. lappend pathlist $part
  492. }
  493. set file [eval file join $pathlist]
  494. if {[file isdirectory $file]} {
  495. return [file join $file $Httpd(-index)]
  496. } else {
  497. return $file
  498. }
  499. }
  500. #
  501. # phttpd::CgiMap --
  502. #
  503. # Decode url-encoded strings.
  504. #
  505. # Arguments:
  506. # data
  507. #
  508. # Side Effects:
  509. # None..
  510. #
  511. # Results:
  512. # None.
  513. #
  514. proc phttpd::CgiMap {data} {
  515. regsub -all {\+} $data { } data
  516. regsub -all {([][$\\])} $data {\\\1} data
  517. regsub -all {%([0-9a-fA-F][0-9a-fA-F])} $data {[format %c 0x\1]} data
  518. return [subst $data]
  519. }
  520. #
  521. # phttpd::QueryMap --
  522. #
  523. # Decode url-encoded query into key/value pairs.
  524. #
  525. # Arguments:
  526. # query
  527. #
  528. # Side Effects:
  529. # None..
  530. #
  531. # Results:
  532. # None.
  533. #
  534. proc phttpd::QueryMap {query} {
  535. set res [list]
  536. regsub -all {[&=]} $query { } query
  537. regsub -all { } $query { {} } query; # Othewise we lose empty values
  538. foreach {key val} $query {
  539. lappend res [CgiMap $key] [CgiMap $val]
  540. }
  541. return $res
  542. }
  543. #
  544. # monitor --
  545. #
  546. # Procedure used to test the phttpd server. It responds on the
  547. # http://<hostname>:<port>/monitor
  548. #
  549. # Arguments:
  550. # array
  551. #
  552. # Side Effects:
  553. # None..
  554. #
  555. # Results:
  556. # None.
  557. #
  558. proc /monitor {array} {
  559. upvar $array data ; # Holds the socket to remote client
  560. #
  561. # Emit headers
  562. #
  563. puts $data(sock) "HTTP/1.0 200 OK"
  564. puts $data(sock) "Date: [phttpd::Date]"
  565. puts $data(sock) "Content-Type: text/html"
  566. puts $data(sock) ""
  567. #
  568. # Emit body
  569. #
  570. puts $data(sock) [subst {
  571. <html>
  572. <body>
  573. <h3>[clock format [clock seconds]]</h3>
  574. }]
  575. after 1 ; # Simulate blocking call
  576. puts $data(sock) [subst {
  577. </body>
  578. </html>
  579. }]
  580. }
  581. # EOF $RCSfile: phttpd.tcl,v $
  582. # Emacs Setup Variables
  583. # Local Variables:
  584. # mode: Tcl
  585. # indent-tabs-mode: nil
  586. # tcl-basic-offset: 4
  587. # End: