# httpd.test - Copyright (c) 2015 Sean Woods # ------------------------------------------------------------------------- source [file join \ [file dirname [file dirname [file join [pwd] [info script]]]] \ devtools testutilities.tcl] testsNeedTcl 8.6 ;# tool requires 8.6 testsNeedTcltest 2 testsNeed TclOO 1 support { use cmdline/cmdline.tcl cmdline use fileutil/fileutil.tcl fileutil use sha1/sha1.tcl sha1 use uri/uri.tcl uri use ncgi/ncgi.tcl ncgi use dns/ip.tcl ip use nettool/nettool.tcl nettool use dicttool/dicttool.tcl dicttool use cron/cron.tcl cron use oodialect/oodialect.tcl oo::dialect use oometa/oometa.tcl oo::meta use tool/index.tcl tool } testing { useLocal httpd.tcl httpd } # ------------------------------------------------------------------------- namespace eval ::httpd {} namespace eval ::httpd::test {} ### # Minimal test harness for the .tests # Not intended for public consumption # (But if you find it handy, please steal!) namespace eval ::httpd::test {} proc ::httpd::test::send {port text} { set sock [socket localhost $port] variable reply set reply($sock) {} chan configure $sock -translation binary -blocking 0 -buffering full -buffersize 4096 chan event $sock readable [list ::httpd::test::get_reply $sock] set headers {} set body {} set read_headers 1 foreach line [split $text \n] { if {$read_headers} { if { $line eq {} } { set read_headers 0 } else { append headers $line \n } } else { append body $line \n } } append headers "Content-Type: text/plain" \n append headers "Content-Length: [string length $body]" \n puts $sock "$headers\n$body" flush $sock while {$reply($sock) eq {}} { update } #vwait [namespace current]::reply($sock) return $reply($sock) } proc ::httpd::test::get_reply {sock} { variable buffer set data [read $sock] append buffer($sock) $data if {[eof $sock]} { chan event $sock readable {} set [namespace current]::reply($sock) $buffer($sock) unset buffer($sock) } } ### # Build the reply class ### tool::class create ::httpd::test::reply { superclass ::httpd::reply method error {code {msg {}}} { my reset my variable data error_codes if {![info exists data(url)]} { set data(url) {} } if {![info exists error_codes($code)]} { set errorstring "Unknown Error Code" } else { set errorstring $error_codes($code) } my reply_headers replace {} my reply_headers set Status: "$code $errorstring" my reply_headers set Content-Type: {text/plain} my puts " $code $errorstring Got the error $code $errorstring while trying to obtain $data(url) " } method reset {} { my variable reply_body my reply_headers replace {Status: {200 OK} Content-Type: text/plain} set reply_body {} } method content {} { my reset switch [my query_headers get REQUEST_URI] { /file { my variable reply_file set reply_file [file join $::here pkgIndex.tcl] } /time { my puts [clock seconds] } /error { error { The programmer asked me to die this way } } /echo - default { my puts [my PostData] } } } ### # Output the result or error to the channel # and destroy this object ### method output {} { my variable reply_body reply_file reply_chan chan chan configure $chan -translation {binary binary} set headers [my reply_headers dump] if {[dict exists $headers Status:]} { set result "[my EncodeStatus [dict get $headers Status:]]\n" } else { set result "[my EncodeStatus {505 Internal Error}]\n" } foreach {key value} $headers { # Ignore Status and Content-length, if given if {$key in {Status: Content-length:}} continue append result "$key $value" \n } if {![info exists reply_file] || [string length $reply_body]} { ### # Return dynamic content ### set reply_body [string trim $reply_body] append result "Content-length: [string length $reply_body]" \n \n append result $reply_body puts -nonewline $chan $result } else { ### # Return a stream of data from a file ### append result "Content-length: [file size $reply_file]" \n \n puts -nonewline $chan $result set reply_chan [open $reply_file r] chan copy $reply_chan $chan catch {close $reply_chan} } chan flush $chan my destroy } } ### # Build the server ### tool::class create httpd::test::app { superclass ::httpd::server property reply_class ::httpd::test::reply } httpd::test::app create TESTAPP port 10001 test httpd-client-0001 {Do an echo request} { set reply [::httpd::test::send 10001 {POST /echo HTTP/1.0 THIS IS MY CODE }] } {HTTP/1.0 200 OK Content-Type: text/plain Connection: close Content-length: 15 THIS IS MY CODE} test httpd-client-0002 {Do another echo request} { set reply [::httpd::test::send 10001 {POST /echo HTTP/1.0 THOUGH THERE ARE MANY LIKE IT }] } {HTTP/1.0 200 OK Content-Type: text/plain Connection: close Content-length: 29 THOUGH THERE ARE MANY LIKE IT} test httpd-client-0003 {Do another echo request} { set reply [::httpd::test::send 10001 {POST /echo HTTP/1.0 THIS ONE ALONE IS MINE }] } {HTTP/1.0 200 OK Content-Type: text/plain Connection: close Content-length: 22 THIS ONE ALONE IS MINE} test httpd-client-0004 {URL Generates Error} { set reply [::httpd::test::send 10001 {POST /error HTTP/1.0 THIS ONE ALONE IS MINE }] } {HTTP/1.0 500 Server Internal Error Content-Type: text/plain Connection: close Content-length: 89 500 Server Internal Error Got the error 500 Server Internal Error while trying to obtain} set checkreply [subst {HTTP/1.0 200 OK Content-Type: text/plain Connection: close Content-length: 10 [clock seconds]}] test httpd-client-0005 {URL Different output with a different request} { set reply [::httpd::test::send 10001 {POST /time HTTP/1.0 THIS ONE ALONE IS MINE }] } $checkreply set fin [open [file join $here pkgIndex.tcl] r] set checkreply [read $fin] close $fin test httpd-client-0006 {Return a file} { set reply [::httpd::test::send 10001 {POST /file HTTP/1.0 }] } "HTTP/1.0 200 OK Content-Type: text/plain Connection: close Content-length: [string length $checkreply] $checkreply" # ------------------------------------------------------------------------- testsuiteCleanup # Local variables: # mode: tcl # indent-tabs-mode: nil # End: