[Xotcl] Multi threaded web-server in XOTcl

Gustaf Neumann neumann at wu-wien.ac.at
Thu Apr 17 16:01:13 CEST 2003


 Fellow XOTcl-community,

 maybe, someone finds the following code useful. The attached file
 implements a simple multi threaded Webserver in XOTcl that 
 uses Zoran's the wonderful tcl thread library 2.5. It is about
 150 lines of code and supports conditional requests (if-modified-since).
 
 best regards
-gustaf neumann
-------------- next part --------------
#!/usr/bin/tclsh8.4
# A very simple multi-threaded Webserver with a thread-pool in XOTcl. 
# It implements only the HTTP GET method with conditional 
# requests (if-modified-since))
# -gustaf neumann            Jan 2003

package require Thread 2.5
package require XOTcl  1.0
namespace import xotcl::*
package require xotcl::serializer

array set opt {-port 8081 -root ./html}
array set opt $argv

#####
##### Definition of the Server Class 
#####
Class Httpd -parameter { {port 80} {root /home/httpd/html/} {maxworkers 8}}
Httpd instproc init args {		;# constructor
  puts stderr "Starting Server; url= http://[info hostname]:[my port]/"
  my set listen [socket -server [list after idle [list [self] accept]] [my port]]
  append initcmd \
    "package require XOTcl 1.0\n" \
    "namespace import xotcl::*\n" \
    [Serializer deepSerialize ::HttpdWrk]
  my set tpid [tpool::create -maxworkers [my maxworkers] -initcmd $initcmd]
}
Httpd instproc destroy {} {		;# destructor
  close [my set listen]			;# close listening port
  tpool::release [my set tpid]
  next
}
Httpd instproc accept {sock ipaddr port} {	;# est. new connection
  thread::detach $sock
  tpool::post -detached [my set tpid] [list \
    HttpdWrk w1 -socket $sock -ipaddr $ipaddr -port $port -root [my root]]
}

#####
##### Definition of the Worker Class 
#####
Class HttpdWrk -parameter {socket port ipaddr root}
HttpdWrk array set codes {		;# we treat these status codes
  200 "Data follows"  304 "Not Modified"  400 "Bad Request"  404 "Not Found"
}
HttpdWrk instproc Date secs {clock format $secs -format {%a, %d %b %Y %T %Z}}
HttpdWrk instproc log {msg} {
  set stamp [clock format [clock seconds] -format "%d/%b/%Y:%H:%M:%S"]
  puts stderr "$stamp thread[thread::id]: $msg"
}
HttpdWrk instproc guessContentType fn {# derive content type from ext.
  switch [file extension $fn] {
    .gif {return image/gif}   .jpg  {return image/jpeg}
    .htm {return text/html}   .html {return text/html} 
    .css {return text/css}    .ps   {return application/postscript}
    default {return text/plain}
  }
}
HttpdWrk instproc replyCode {code} {
  my set code $code
  set msg [[self class] set codes($code)]
  puts [my socket] "HTTP/1.0 $code $msg\r\n\Date: [my Date [clock seconds]]"
  if {$code >= 300} {
    set p [expr {[my exists path] ? [my set path] : "-unknown-" }]
    my sendDynamicString "\n<title>Error: $code</title>\n\
      Error $code: <b>$msg</b><br>\nUrl: $p\r\n"
  }
}
HttpdWrk instproc init args {
  my set startTime [clock clicks -milliseconds]
  thread::attach [my socket]
  if {[catch {my processRequest} errMsg]} {
    puts stderr "error=$errMsg\n$::errorInfo"
  }
}
HttpdWrk instproc processRequest {} {
  set n [gets [my socket] line]
  my instvar method path fileName 
  #my log $line
  if {[regexp {^(GET) +([^ ]+) +HTTP/.*$} $line _ method path]} {
    set fileName [my root]/$path   ;# construct filename
    regsub {/$} $fileName /index.html fileName
    my header
  } elseif {$n<0} {
    set code -1
    my close
  } else { 
    my replyCode 400 
  }
}
HttpdWrk instproc header {} {	;# process the header
  while 1 {
    set n [gets [my socket] line]
    if {$n == 0} break
    if {[regexp {^([^:]+): *(.+)$} $line _ key value]} {
      my set meta([string tolower $key]) $value
    }
  }
  my response
}
HttpdWrk instproc response {} {  ;# Respond to the GET-query
  my instvar fileName
  if {[file readable $fileName]} {
    if {[my unmodified [file mtime $fileName]]} { 
      my replyCode 304
    } else { 
      my replyCode 200
      my sendFile
    }
  } else {
    my replyCode 404
  }
}
HttpdWrk instproc unmodified mtime {
  if {[my exists meta(if-modified-since)]} {
    set ms [my set meta(if-modified-since)]
    regexp {^([^;]+);} $ms _ ms
    if {![catch {set mss [clock scan $ms]}]} {return [expr {$mtime <= $mss}]}
  }
  return 0
}
HttpdWrk instproc sendFile {} {
  my instvar fileName socket
  puts $socket "Last-Modified: [my Date [file mtime $fileName]]\r\n\
     Content-Type: [my guessContentType $fileName]\r\n\
     Content-Length: [file size $fileName]\r\n"
  set localFile [open $fileName r]
  fconfigure $socket -translation binary -buffersize 16000
  fconfigure $localFile -translation binary -buffersize 16000
  fcopy $localFile $socket -command [list [self] sendFile-end $localFile]
  my vwait done
}
HttpdWrk instproc sendFile-end {localFile args} {
  close $localFile
  my close
  my set done 1
}
HttpdWrk instproc sendDynamicString {content {contentType text/html}} {
  puts [my socket] "Content-Type: $contentType\r\n\
     Content-Length: [string length $content]\r\n"
  fconfigure [my socket] -translation lf
  puts -nonewline [my socket] $content
  my close
}
HttpdWrk instproc close {} {		;# close a request
  set elapsed [expr {[clock clicks -milliseconds]-[my set startTime]}]
  my log "close ($elapsed ms) [my set code]"
  close [my socket]
}
proc bgerror {args} {
  puts stderr "$::argv0 background error: $args"
  puts stderr "\t$::errorInfo\nerrorCode = $::errorCode"
}

Httpd h1 -port $opt(-port) -root $opt(-root)
vwait forever


More information about the Xotcl mailing list