Tcl Library Source Code

Changes On Branch aku-the-big-httpd-testsuite-cleanup
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch aku-the-big-httpd-testsuite-cleanup Excluding Merge-Ins

This is equivalent to a diff from 6ae00eec07 to 367088a3b3

2019-06-05
05:02
Integrated testsuite fixups Merged latest work from trunk Regenerated documentation. - Fixed outdated references to feedback.inc Use `modules/common-text/feedback.inc` now. check-in: e3ae2af3f4 user: aku tags: hypnotoad
2019-06-04
20:08
Added demonstrations of passing configuration parameters and seeing the data reflected in behavior in httpd Closed-Leaf check-in: 367088a3b3 user: hypnotoad tags: aku-the-big-httpd-testsuite-cleanup
19:50
Removing hardcoded path, and registering the paths in the appropriate manner in the httpd tests check-in: 357caec02f user: hypnotoad tags: aku-the-big-httpd-testsuite-cleanup
07:34
Starting to investigate the issue with `scgi-client-0006` I was unable to help myself and started a cleanup of the tests first. - Fixed up indentation all over the place. - Moved all the various expected replies (404, 200, 500) into constructor procedures. This not only made the individual tests clearer, but also easier to properly indent. - Removed the custom `httpd::test::compare` (HTC). Switched to regular tcltest glob matching, plus string map for basic templating of some expected results (200... variants). - The thing with the `norm-eol` I had to add (*) makes me suspect that HTC hid a possible bug in the proxy coding around the handling of line endings. (Of course, my norm-eol now works around the same thing, if in a different manner). (Ad *) See marker `AKU` in the code. - Moved the cgi helper application into an `assets` directory. No need for dynamic creation and deletion. Just tweak the `path` it is looked for. With all this I can now easy see that `scgi-client-0006` returns a 500 server error to us, instead of the expected file. check-in: 91d4900a3d user: aku tags: aku-the-big-httpd-testsuite-cleanup
2019-06-03
23:44
Update to clay, practcl, and httpd brought over from the clay project. clay - fixes a typo in the ensemble generator - Removed a call to cron::object_destroy (we don't always run with cron) - Added a tool-like event manager - Removed where the amalgamater was adding dictargs twice - Added a "short" uuid for local projects httpd - updates to clay - Test fixes - Remove CSS templating from the vanilla httpd - Some behind the scenes tweaks to accomodate the cuneiform module when httpd is used in external packages practcl - updates to clay - Static packages are now advertised in the tclpreinitscript, eliminating the need for the master process to throw a bootstrap to new threads check-in: 6ae00eec07 user: hypnotoad tags: hypnotoad
2019-05-30
18:22
Updated clay to mirror the latest version on http://fossil.etoyoc.com/fossil/clay (0.8) * Adds support for short UUIDs * Adds support for the embedded md5 generator in odielibc * Integrates dictargs into the clay syntax Update practcl * Embedded clay version is now 0.8 * Fixes a bug in installModule for an empty directory * Fixes a longstanding issue with bootstrapping child interps and threads from kits check-in: 0aa60d1fa8 user: hypnotoad tags: hypnotoad

Changes to modules/clay/build/event.tcl.



1
2
3
4
5
6
7
8
9
10
11
12
13
14

15
16
17
18
19
20
21
22
23
24
25
26
27





























28
29
30
31
32
33
34


::namespace eval ::clay::event {}

###
# Mark an object for destruction on the next cleanup
###
proc ::clay::destroy args {
  if {![info exists ::clay::idle_destroy]} {
    set ::clay::idle_destroy {}
  }
  foreach object $args {
    if {$object in $::clay::idle_destroy} continue
    lappend ::clay::idle_destroy  $object
  }
}


###
# Process the queue of objects to be destroyed
###
proc ::clay::cleanup {} {
  if {![info exists ::clay::idle_destroy]} return
  foreach obj $::clay::idle_destroy {
    if {[info commands $obj] ne {}} {
      catch {$obj destroy}
    }
  }
  set ::clay::idle_destroy {}
}






























###
# description: Cancel a scheduled event
###
proc ::clay::event::cancel {self {task *}} {
  variable timer_event
  variable timer_script
>
>
|
<
<
<
<
|
<
<
|
<
<
<
<
<
>













>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1
2
3




4


5





6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
if {[info commands ::cron::object_destroy] eq {}} {
  # Provide a noop if we aren't running with the cron scheduler
  namespace eval ::cron {}




  proc ::cron::object_destroy args {}


}





::namespace eval ::clay::event {}

###
# Process the queue of objects to be destroyed
###
proc ::clay::cleanup {} {
  if {![info exists ::clay::idle_destroy]} return
  foreach obj $::clay::idle_destroy {
    if {[info commands $obj] ne {}} {
      catch {$obj destroy}
    }
  }
  set ::clay::idle_destroy {}
}

proc ::clay::object_create {objname {class {}}} {
  #if {$::clay::trace>0} {
  #  puts [list $objname CREATE]
  #}
}

proc ::clay::object_rename {object newname} {
  if {$::clay::trace>0} {
    puts [list $object RENAME -> $newname]
  }
}

###
# Mark an objects for destruction on the next cleanup
###
proc ::clay::object_destroy args {
  if {![info exists ::clay::idle_destroy]} {
    set ::clay::idle_destroy {}
  }
  foreach objname $args {
    if {$::clay::trace>0} {
      puts [list $objname DESTROY]
    }
    ::cron::object_destroy $objname
    if {$objname in $::clay::idle_destroy} continue
    lappend ::clay::idle_destroy $objname
  }
}

###
# description: Cancel a scheduled event
###
proc ::clay::event::cancel {self {task *}} {
  variable timer_event
  variable timer_script

Changes to modules/clay/build/metaclass.tcl.

105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
proc ::clay::define::destructor rawbody {
  set body {
# Run the destructor once and only once
set self [self]
my variable DestroyEvent
if {$DestroyEvent} return
set DestroyEvent 1
::clay::object_destroy $self
}
  append body $rawbody
  ::oo::define [current_class] destructor $body
}

proc ::clay::define::Dict {name {values {}}} {
  set class [current_class]







<







105
106
107
108
109
110
111

112
113
114
115
116
117
118
proc ::clay::define::destructor rawbody {
  set body {
# Run the destructor once and only once
set self [self]
my variable DestroyEvent
if {$DestroyEvent} return
set DestroyEvent 1

}
  append body $rawbody
  ::oo::define [current_class] destructor $body
}

proc ::clay::define::Dict {name {values {}}} {
  set class [current_class]
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#    (if missing) when the object changes class via the [emph morph] method.
###
proc ::clay::define::Variable {name {default {}}} {
  set class [current_class]
  set name [string trimright $name :/]
  $class clay set variable/ $name $default
}

proc ::clay::object_create {objname {class {}}} {
  #if {$::clay::trace>0} {
  #  puts [list $objname CREATE]
  #}
}

proc ::clay::object_rename {object newname} {
  if {$::clay::trace>0} {
    puts [list $object RENAME -> $newname]
  }
}

proc ::clay::object_destroy objname {
  if {$::clay::trace>0} {
    puts [list $objname DESTROY]
  }
  #::cron::object_destroy $objname
}








<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
190
191
192
193
194
195
196




















#    (if missing) when the object changes class via the [emph morph] method.
###
proc ::clay::define::Variable {name {default {}}} {
  set class [current_class]
  set name [string trimright $name :/]
  $class clay set variable/ $name $default
}




















Changes to modules/clay/build/object.tcl.

457
458
459
460
461
462
463


















464
465
466
467
468
469
470
        }
        foreach class $clayorder {
          if {[$class clay exists {*}$args]} {
            return $class
          }
        }
        return {}


















      }
      replace {
        set clay [lindex $args 0]
      }
      source {
        source [lindex $args 0]
      }







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
        }
        foreach class $clayorder {
          if {[$class clay exists {*}$args]} {
            return $class
          }
        }
        return {}
      }
      refcount {
        my variable refcount
        if {![info exists refcount]} {
          return 0
        }
        return $refcount
      }
      refcount_incr {
        my variable refcount
        incr refcount
      }
      refcount_decr {
        my variable refcount
        incr refcount -1
        if {$refcount <= 0} {
          ::clay::object_destroy [self]
        }
      }
      replace {
        set clay [lindex $args 0]
      }
      source {
        source [lindex $args 0]
      }

Changes to modules/clay/clay.man.

443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476













477
478
479
480
481
482
483
    [example {property variable NAME {default DEFAULT}}]
    [para]
    Variables registered in the variable property are also initialized
    (if missing) when the object changes class via the [emph morph] method.




[call proc [cmd clay::object_create] [arg objname] [opt "[arg class] [const ""]"]]


[call proc [cmd clay::object_rename] [arg object] [arg newname]]


[call proc [cmd clay::object_destroy] [arg objname]]


[call proc [cmd clay::ensemble_methodbody] [arg ensemble] [arg einfo]]


[call proc [cmd clay::define::Ensemble] [arg rawmethod] [opt "[arg args]"]]


[call proc [cmd clay::destroy] [opt "[arg args]"]]

 Mark an object for destruction on the next cleanup




[call proc [cmd clay::cleanup]]

 Process the queue of objects to be destroyed

















[call proc [cmd clay::event::cancel] [arg self] [opt "[arg task] [const "*"]"]]

Cancel a scheduled event








<
<
<
<
<
<
<
<
<






<
<
<
<
<
<
<





>
>
>
>
>
>
>
>
>
>
>
>
>







443
444
445
446
447
448
449









450
451
452
453
454
455







456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
    [example {property variable NAME {default DEFAULT}}]
    [para]
    Variables registered in the variable property are also initialized
    (if missing) when the object changes class via the [emph morph] method.













[call proc [cmd clay::ensemble_methodbody] [arg ensemble] [arg einfo]]


[call proc [cmd clay::define::Ensemble] [arg rawmethod] [opt "[arg args]"]]









[call proc [cmd clay::cleanup]]

 Process the queue of objects to be destroyed




[call proc [cmd clay::object_create] [arg objname] [opt "[arg class] [const ""]"]]


[call proc [cmd clay::object_rename] [arg object] [arg newname]]


[call proc [cmd clay::object_destroy] [opt "[arg args]"]]

 Mark an objects for destruction on the next cleanup




[call proc [cmd clay::event::cancel] [arg self] [opt "[arg task] [const "*"]"]]

Cancel a scheduled event

Changes to modules/clay/clay.tcl.

1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
proc ::clay::define::destructor rawbody {
  set body {
# Run the destructor once and only once
set self [self]
my variable DestroyEvent
if {$DestroyEvent} return
set DestroyEvent 1
::clay::object_destroy $self
}
  append body $rawbody
  ::oo::define [current_class] destructor $body
}
proc ::clay::define::Dict {name {values {}}} {
  set class [current_class]
  set name [string trim $name :/]







<







1025
1026
1027
1028
1029
1030
1031

1032
1033
1034
1035
1036
1037
1038
proc ::clay::define::destructor rawbody {
  set body {
# Run the destructor once and only once
set self [self]
my variable DestroyEvent
if {$DestroyEvent} return
set DestroyEvent 1

}
  append body $rawbody
  ::oo::define [current_class] destructor $body
}
proc ::clay::define::Dict {name {values {}}} {
  set class [current_class]
  set name [string trim $name :/]
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
  }
}
proc ::clay::define::Variable {name {default {}}} {
  set class [current_class]
  set name [string trimright $name :/]
  $class clay set variable/ $name $default
}
proc ::clay::object_create {objname {class {}}} {
  #if {$::clay::trace>0} {
  #  puts [list $objname CREATE]
  #}
}
proc ::clay::object_rename {object newname} {
  if {$::clay::trace>0} {
    puts [list $object RENAME -> $newname]
  }
}
proc ::clay::object_destroy objname {
  if {$::clay::trace>0} {
    puts [list $objname DESTROY]
  }
  #::cron::object_destroy $objname
}

###
# END: metaclass.tcl
###
###
# START: ensemble.tcl
###







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1087
1088
1089
1090
1091
1092
1093
















1094
1095
1096
1097
1098
1099
1100
  }
}
proc ::clay::define::Variable {name {default {}}} {
  set class [current_class]
  set name [string trimright $name :/]
  $class clay set variable/ $name $default
}

















###
# END: metaclass.tcl
###
###
# START: ensemble.tcl
###
1769
1770
1771
1772
1773
1774
1775


















1776
1777
1778
1779
1780
1781
1782
        }
        foreach class $clayorder {
          if {[$class clay exists {*}$args]} {
            return $class
          }
        }
        return {}


















      }
      replace {
        set clay [lindex $args 0]
      }
      source {
        source [lindex $args 0]
      }







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
        }
        foreach class $clayorder {
          if {[$class clay exists {*}$args]} {
            return $class
          }
        }
        return {}
      }
      refcount {
        my variable refcount
        if {![info exists refcount]} {
          return 0
        }
        return $refcount
      }
      refcount_incr {
        my variable refcount
        incr refcount
      }
      refcount_decr {
        my variable refcount
        incr refcount -1
        if {$refcount <= 0} {
          ::clay::object_destroy [self]
        }
      }
      replace {
        set clay [lindex $args 0]
      }
      source {
        source [lindex $args 0]
      }
1905
1906
1907
1908
1909
1910
1911


1912
1913
1914
1915
1916
1917
1918
1919
1920
1921

1922
1923
1924
1925
1926
1927
1928
1929
1930























1931
1932
1933
1934
1935
1936
1937

###
# END: object.tcl
###
###
# START: event.tcl
###


::namespace eval ::clay::event {
}
proc ::clay::destroy args {
  if {![info exists ::clay::idle_destroy]} {
    set ::clay::idle_destroy {}
  }
  foreach object $args {
    if {$object in $::clay::idle_destroy} continue
    lappend ::clay::idle_destroy  $object
  }

}
proc ::clay::cleanup {} {
  if {![info exists ::clay::idle_destroy]} return
  foreach obj $::clay::idle_destroy {
    if {[info commands $obj] ne {}} {
      catch {$obj destroy}
    }
  }
  set ::clay::idle_destroy {}























}
proc ::clay::event::cancel {self {task *}} {
  variable timer_event
  variable timer_script

  foreach {id event} [array get timer_event $self:$task] {
    ::after cancel $event







>
>
|
<
|
<
<
|
<
<
<
<
>









>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1906
1907
1908
1909
1910
1911
1912
1913
1914
1915

1916


1917




1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957

###
# END: object.tcl
###
###
# START: event.tcl
###
if {[info commands ::cron::object_destroy] eq {}} {
  # Provide a noop if we aren't running with the cron scheduler
  namespace eval ::cron {}

  proc ::cron::object_destroy args {}


}




::namespace eval ::clay::event {
}
proc ::clay::cleanup {} {
  if {![info exists ::clay::idle_destroy]} return
  foreach obj $::clay::idle_destroy {
    if {[info commands $obj] ne {}} {
      catch {$obj destroy}
    }
  }
  set ::clay::idle_destroy {}
}
proc ::clay::object_create {objname {class {}}} {
  #if {$::clay::trace>0} {
  #  puts [list $objname CREATE]
  #}
}
proc ::clay::object_rename {object newname} {
  if {$::clay::trace>0} {
    puts [list $object RENAME -> $newname]
  }
}
proc ::clay::object_destroy args {
  if {![info exists ::clay::idle_destroy]} {
    set ::clay::idle_destroy {}
  }
  foreach objname $args {
    if {$::clay::trace>0} {
      puts [list $objname DESTROY]
    }
    ::cron::object_destroy $objname
    if {$objname in $::clay::idle_destroy} continue
    lappend ::clay::idle_destroy $objname
  }
}
proc ::clay::event::cancel {self {task *}} {
  variable timer_event
  variable timer_script

  foreach {id event} [array get timer_event $self:$task] {
    ::after cancel $event

Added modules/httpd/assets/test_cgi.tcl.





























>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/tclsh

puts stdout "Status: 200 OK"
if {$::env(CONTENT_LENGTH) > 0} {
  puts stdout "Content-Type: $::env(CONTENT_TYPE)"
  set dat [read stdin $::env(CONTENT_LENGTH)]
} else {
  puts stdout "Content-Type: text/plain"
  set dat "Hi!"
}
puts stdout "Content-Length: [string length $dat]"
puts stdout {}
puts stdout $dat
exit 0

Changes to modules/httpd/build/cgi.tcl.

90
91
92
93
94
95
96

97
98
99
100
101
102
103
      ###
      # Send any POST/PUT/etc content
      ###
      my ChannelCopy $chana $chanb -size $length
    } else {
      chan flush $chanb
    }

    chan event $chanb readable [info coroutine]
    yield
  }


  method ProxyReply {chana chanb args} {
    my log ProxyReply [list args $args]







>







90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
      ###
      # Send any POST/PUT/etc content
      ###
      my ChannelCopy $chana $chanb -size $length
    } else {
      chan flush $chanb
    }
    my clay refcount_incr
    chan event $chanb readable [info coroutine]
    yield
  }


  method ProxyReply {chana chanb args} {
    my log ProxyReply [list args $args]
120
121
122
123
124
125
126

127
128
129
130
131
132
133
134
135
136
    chan puts $chanb $replybuffer
    ###
    # Output the body. With no -size flag, channel will copy until EOF
    ###
    chan configure $chana -translation binary -blocking 0 -buffering full -buffersize 4096
    chan configure $chanb -translation binary -blocking 0 -buffering full -buffersize 4096
    my ChannelCopy $chana $chanb -chunk 4096

  }

  ###
  # For most CGI applications a directory list is vorboten
  ###
  method DirectoryListing {local_file} {
    my error 403 {Not Allowed}
    tailcall my DoOutput
  }
}







>










121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
    chan puts $chanb $replybuffer
    ###
    # Output the body. With no -size flag, channel will copy until EOF
    ###
    chan configure $chana -translation binary -blocking 0 -buffering full -buffersize 4096
    chan configure $chanb -translation binary -blocking 0 -buffering full -buffersize 4096
    my ChannelCopy $chana $chanb -chunk 4096
    my clay refcount_decr
  }

  ###
  # For most CGI applications a directory list is vorboten
  ###
  method DirectoryListing {local_file} {
    my error 403 {Not Allowed}
    tailcall my DoOutput
  }
}

Changes to modules/httpd/build/core.tcl.

30
31
32
33
34
35
36


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72



73
74
75
76
77
78
79
###
# A metaclass for MIME handling behavior across a live socket
###
clay::define ::httpd::mime {


  method ChannelCopy {in out args} {


    set chunk 4096
    set size -1
    foreach {f v} $args {
      set [string trim $f -] $v
    }
    dict set info coroutine [info coroutine]
    if {$size>0 && $chunk>$size} {
        set chunk $size
    }
    set bytes 0
    set sofar 0
    set method [self method]
    while 1 {
      set command {}
      set error {}
      if {$size>=0} {
        incr sofar $bytes
        set remaining [expr {$size-$sofar}]
        if {$remaining <= 0} {
          break
        } elseif {$chunk > $remaining} {
          set chunk $remaining
        }
      }
      lassign [yieldto chan copy $in $out -size $chunk \
        -command [list [info coroutine] $method]] \
        command bytes error
      if {$command ne $method} {
        error "Subroutine $method interrupted"
      }
      if {[string length $error]} {
        error $error
      }
      if {[chan eof $in]} {
        break
      }



    }
  }

  ###
  # Returns a block of HTML
  method html_header {{title {}} args} {
    set result {}







>
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>







30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
###
# A metaclass for MIME handling behavior across a live socket
###
clay::define ::httpd::mime {


  method ChannelCopy {in out args} {
    try {
      my clay refcount_incr
      set chunk 4096
      set size -1
      foreach {f v} $args {
        set [string trim $f -] $v
      }
      dict set info coroutine [info coroutine]
      if {$size>0 && $chunk>$size} {
          set chunk $size
      }
      set bytes 0
      set sofar 0
      set method [self method]
      while 1 {
        set command {}
        set error {}
        if {$size>=0} {
          incr sofar $bytes
          set remaining [expr {$size-$sofar}]
          if {$remaining <= 0} {
            break
          } elseif {$chunk > $remaining} {
            set chunk $remaining
          }
        }
        lassign [yieldto chan copy $in $out -size $chunk \
          -command [list [info coroutine] $method]] \
          command bytes error
        if {$command ne $method} {
          error "Subroutine $method interrupted"
        }
        if {[string length $error]} {
          error $error
        }
        if {[chan eof $in]} {
          break
        }
      }
    } finally {
      my clay refcount_decr
    }
  }

  ###
  # Returns a block of HTML
  method html_header {{title {}} args} {
    set result {}
297
298
299
300
301
302
303

304
305
306
307
308
309
310
311

312
313
314
      }
    }
    return $pathlist
  }


  method wait {mode sock} {

    if {[info coroutine] eq {}} {
      chan event $sock $mode [list set ::httpd::lock_$sock $mode]
      vwait ::httpd::lock_$sock
    } else {
      chan event $sock $mode [info coroutine]
      yield
    }
    chan event $sock $mode {}

  }

}







>








>



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
      }
    }
    return $pathlist
  }


  method wait {mode sock} {
    my clay refcount_incr
    if {[info coroutine] eq {}} {
      chan event $sock $mode [list set ::httpd::lock_$sock $mode]
      vwait ::httpd::lock_$sock
    } else {
      chan event $sock $mode [info coroutine]
      yield
    }
    chan event $sock $mode {}
    my clay refcount_decr
  }

}

Changes to modules/httpd/build/dispatch.tcl.

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  }
}

::clay::define ::httpd::content.cache {

  method Dispatch {} {
    my variable chan
    try {
      my wait writable $chan
      chan configure $chan  -translation {binary binary}
      chan puts -nonewline $chan [my clay get cache/ data]
    } on error {err info} {
      my <server> debug [dict get $info -errorinfo]
    } finally {
      my TransferComplete $chan
    }
  }
}

::clay::define ::httpd::content.template {

  method content {} {
    if {[my request get HTTP_STATUS] ne {}} {







<
|
|
|
<
<
<
<
<







25
26
27
28
29
30
31

32
33
34





35
36
37
38
39
40
41
  }
}

::clay::define ::httpd::content.cache {

  method Dispatch {} {
    my variable chan

    my wait writable $chan
    chan configure $chan  -translation {binary binary}
    chan puts -nonewline $chan [my clay get cache/ data]





  }
}

::clay::define ::httpd::content.template {

  method content {} {
    if {[my request get HTTP_STATUS] ne {}} {

Changes to modules/httpd/build/file.tcl.

127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

145
146
147
148
149
150
151
152
153
154
155
      tailcall my DoOutput
    }
    if {$chan eq {}} return
    my wait writable $chan
    if {![info exists reply_file]} {
      tailcall my DoOutput
    }
    try {
      chan configure $chan  -translation {binary binary}
      my log HttpAccess {}
      ###
      # Return a stream of data from a file
      ###
      set size [file size $reply_file]
      my reply set Content-Length $size
      append result [my reply output] \n
      chan puts -nonewline $chan $result
      set reply_chan [open $reply_file r]

      my log SendReply [list length $size]
      ###
      # Output the file contents. With no -size flag, channel will copy until EOF
      ###
      chan configure $reply_chan -translation {binary binary} -buffersize 4096 -buffering full -blocking 0
      my ChannelCopy $reply_chan $chan -chunk 4096
    } finally {
      my TransferComplete $reply_chan $chan
    }
  }
}







<
|
|
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
<
<
<


127
128
129
130
131
132
133

134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150



151
152
      tailcall my DoOutput
    }
    if {$chan eq {}} return
    my wait writable $chan
    if {![info exists reply_file]} {
      tailcall my DoOutput
    }

    chan configure $chan  -translation {binary binary}
    my log HttpAccess {}
    ###
    # Return a stream of data from a file
    ###
    set size [file size $reply_file]
    my reply set Content-Length $size
    append result [my reply output] \n
    chan puts -nonewline $chan $result
    set reply_chan [open $reply_file r]
    my ChannelRegister $reply_chan
    my log SendReply [list length $size]
    ###
    # Output the file contents. With no -size flag, channel will copy until EOF
    ###
    chan configure $reply_chan -translation {binary binary} -buffersize 4096 -buffering full -blocking 0
    my ChannelCopy $reply_chan $chan -chunk 4096



  }
}

Changes to modules/httpd/build/plugin.tcl.

199
200
201
202
203
204
205
206
207
208
209
210
    }
    $pageobj clay mixinmap {*}$mixinmap
    if {[dict exists $reply delegate]} {
      $pageobj clay delegate {*}[dict get $reply delegate]
    }
    $pageobj dispatch $sock $reply
    set output [$pageobj output]
    catch {$pageobj destroy}
    return $output
  }
}








|




199
200
201
202
203
204
205
206
207
208
209
210
    }
    $pageobj clay mixinmap {*}$mixinmap
    if {[dict exists $reply delegate]} {
      $pageobj clay delegate {*}[dict get $reply delegate]
    }
    $pageobj dispatch $sock $reply
    set output [$pageobj output]
    $pageobj clay refcount_decr
    return $output
  }
}

Changes to modules/httpd/build/proxy.tcl.

155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
    if {$sock eq {}} {
      my error 404 {Not Found}
      tailcall my DoOutput
    }
    my log HttpAccess {}
    chan event $sock writable [info coroutine]
    yield
    try {
      my ProxyRequest $chan $sock
      my ProxyReply   $sock $chan
    } finally {
      my TransferComplete $chan $sock
    }
  }
}







|
|
|
<
<
<


155
156
157
158
159
160
161
162
163
164



165
166
    if {$sock eq {}} {
      my error 404 {Not Found}
      tailcall my DoOutput
    }
    my log HttpAccess {}
    chan event $sock writable [info coroutine]
    yield
    my ChannelRegister $sock
    my ProxyRequest $chan $sock
    my ProxyReply   $sock $chan



  }
}

Changes to modules/httpd/build/reply.man.

129
130
131
132
133
134
135





136
137
138
139
140
141
142

Terminate the transaction, and close the socket.

[call method [cmd HttpHeaders] [arg sock] [arg ?debug?]]

Stream MIME headers from the socket [arg sock], stopping at an empty line. Returns
the stream as a block of text.






[call method [cmd dispatch] [arg newsock] [arg datastate]]

Take over control of the socket [arg newsock], and store that as the [arg chan] variable
for the object. This method runs through all of the steps of reading HTTP headers, generating
content, and closing the connection. (See class writetup).








>
>
>
>
>







129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147

Terminate the transaction, and close the socket.

[call method [cmd HttpHeaders] [arg sock] [arg ?debug?]]

Stream MIME headers from the socket [arg sock], stopping at an empty line. Returns
the stream as a block of text.

[call method [cmd ChannelRegister] [arg chan]]

Registers a channel that will need to be flushed and closed when the object's destructor
invokes the close method.

[call method [cmd dispatch] [arg newsock] [arg datastate]]

Take over control of the socket [arg newsock], and store that as the [arg chan] variable
for the object. This method runs through all of the steps of reading HTTP headers, generating
content, and closing the connection. (See class writetup).

264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
a timeout error to the request if it has, as well as destroy the object and close the
[arg chan] socket.

[call method [cmd timestamp]]

Return the current system time in the format: [example {%a, %d %b %Y %T %Z}]

[call method [cmd TransferComplete] [arg args]]

Intended to be invoked from [cmd {chan copy}] as a callback. This closes every channel
fed to it on the command line, and then destroys the object.

[example {
    ###
    # Output the body
    ###
    chan configure $sock -translation binary -blocking 0 -buffering full -buffersize 4096
    chan configure $chan -translation binary -blocking 0 -buffering full -buffersize 4096
    if {$length} {
      ###
      # Send any POST/PUT/etc content
      ###
      chan copy $sock $chan -size $SIZE -command [info coroutine]
      yield
    }
    catch {close $sock}
    chan flush $chan
}]

[call method [cmd Url_Decode] [arg string]]

De-httpizes a string.

[list_end]







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<





269
270
271
272
273
274
275






















276
277
278
279
280
a timeout error to the request if it has, as well as destroy the object and close the
[arg chan] socket.

[call method [cmd timestamp]]

Return the current system time in the format: [example {%a, %d %b %Y %T %Z}]























[call method [cmd Url_Decode] [arg string]]

De-httpizes a string.

[list_end]

Changes to modules/httpd/build/reply.tcl.

95
96
97
98
99
100
101

102
103
104
105
106
107
108
# 	}
# }
#
# }]
###
::clay::define ::httpd::reply {
  superclass ::httpd::mime


  Delegate <server> {
    description {The server object which spawned this reply}
  }

  ###
  # A dictionary which will converted into the MIME headers of the reply







>







95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# 	}
# }
#
# }]
###
::clay::define ::httpd::reply {
  superclass ::httpd::mime
  Variable ChannelRegister {}

  Delegate <server> {
    description {The server object which spawned this reply}
  }

  ###
  # A dictionary which will converted into the MIME headers of the reply
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142













143
144
145
146
147
148
149



150
151
152
153
154
155

156
157
158
159
160
161
162
    REMOTE_ADDR {}
    REMOTE_HOST {}
    USER_AGENT {}
    SESSION {}
  }

  constructor {ServerObj args} {
    my variable chan dispatched_time uuid
    set uuid [namespace tail [self]]
    set dispatched_time [clock milliseconds]
    my clay delegate <server> $ServerObj
    foreach {field value} [::clay::args_to_options {*}$args] {
      my clay set config $field: $value
    }
  }

  ###
  # clean up on exit
  ###
  destructor {
    my close
  }














  ###
  # Close channels opened by this object
  ###
  method close {} {
    my variable chan
    if {[info exists chan] && $chan ne {}} {



      catch {chan event $chan readable {}}
      catch {chan event $chan writable {}}
      catch {chan flush $chan}
      catch {chan close $chan}
      set chan {}
    }

  }

  ###
  # Record a dispatch event
  ###
  method Log_Dispatched {} {
    my log Dispatched [dict create \







|














>
>
>
>
>
>
>
>
>
>
>
>
>





|
|
>
>
>
|
|
|
|
<

>







122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170

171
172
173
174
175
176
177
178
179
    REMOTE_ADDR {}
    REMOTE_HOST {}
    USER_AGENT {}
    SESSION {}
  }

  constructor {ServerObj args} {
    my variable dispatched_time uuid
    set uuid [namespace tail [self]]
    set dispatched_time [clock milliseconds]
    my clay delegate <server> $ServerObj
    foreach {field value} [::clay::args_to_options {*}$args] {
      my clay set config $field: $value
    }
  }

  ###
  # clean up on exit
  ###
  destructor {
    my close
  }

  # Registers a channel to be closed by the close method
  method ChannelRegister args {
    my variable ChannelRegister
    if {![info exists ChannelRegister]} {
      set ChannelRegister {}
    }
    foreach c $args {
      if {$c ni $ChannelRegister} {
        lappend ChannelRegister $c
      }
    }
  }

  ###
  # Close channels opened by this object
  ###
  method close {} {
    my variable ChannelRegister
    if {![info exists ChannelRegister]} {
      return
    }
    foreach c $ChannelRegister {
      catch {chan event $c readable {}}
      catch {chan event $c writable {}}
      catch {chan flush $c}
      catch {chan close $c}

    }
    set ChannelRegister {}
  }

  ###
  # Record a dispatch event
  ###
  method Log_Dispatched {} {
    my log Dispatched [dict create \
183
184
185
186
187
188
189

190

191
192
193
194
195
196
197
  # ensemble
  # [para]
  # All other fields are passed along to the [method clay] structure of the object.
  ###
  method dispatch {newsock datastate} {
    my variable chan request
    try {

      set chan $newsock

      chan event $chan readable {}
      chan configure $chan -translation {auto crlf} -buffering line
      if {[dict exists $datastate mixin]} {
        set mixinmap [dict get $datastate mixin]
      } else {
        set mixinmap {}
      }







>

>







200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
  # ensemble
  # [para]
  # All other fields are passed along to the [method clay] structure of the object.
  ###
  method dispatch {newsock datastate} {
    my variable chan request
    try {
      my clay refcount_incr
      set chan $newsock
      my ChannelRegister $chan
      chan event $chan readable {}
      chan configure $chan -translation {auto crlf} -buffering line
      if {[dict exists $datastate mixin]} {
        set mixinmap [dict get $datastate mixin]
      } else {
        set mixinmap {}
      }
219
220
221
222
223
224
225



226
227
228
229
230
231
232
      }
      my Session_Load
      my Log_Dispatched
      my Dispatch
    } on error {err errdat} {
      my error 500 $err [dict get $errdat -errorinfo]
      my DoOutput



    }
  }

  method Dispatch {} {
    # Invoke the URL implementation.
    my content
    my DoOutput







>
>
>







238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
      }
      my Session_Load
      my Log_Dispatched
      my Dispatch
    } on error {err errdat} {
      my error 500 $err [dict get $errdat -errorinfo]
      my DoOutput
    } finally {
      my close
      my clay refcount_decr
    }
  }

  method Dispatch {} {
    # Invoke the URL implementation.
    my content
    my DoOutput
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
        append result $reply_body
      } else {
        append result [my reply output]
      }
      chan puts -nonewline $chan $result
      my log HttpAccess {}
    }
    my destroy
  }

  ###
  # For GET requests, converts the QUERY_DATA header into a key/value list.
  #
  # For POST requests, reads the Post data and converts that information to
  # a key/value list for application/x-www-form-urlencoded posts. For multipart







<







356
357
358
359
360
361
362

363
364
365
366
367
368
369
        append result $reply_body
      } else {
        append result [my reply output]
      }
      chan puts -nonewline $chan $result
      my log HttpAccess {}
    }

  }

  ###
  # For GET requests, converts the QUERY_DATA header into a key/value list.
  #
  # For POST requests, reads the Post data and converts that information to
  # a key/value list for application/x-www-form-urlencoded posts. For multipart
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
    }
    return $postdata
  }

  # Manage session data
  method Session_Load {} {}



  # Intended to be invoked from [cmd {chan copy}] as a callback. This closes every channel
  # fed to it on the command line, and then destroys the object.
  #
  # [example {
  #     ###
  #     # Output the body
  #     ###
  #     chan configure $sock -translation binary -blocking 0 -buffering full -buffersize 4096
  #     chan configure $chan -translation binary -blocking 0 -buffering full -buffersize 4096
  #     if {$length} {
  #       ###
  #       # Send any POST/PUT/etc content
  #       ###
  #       chan copy $sock $chan -size $SIZE -command [info coroutine]
  #       yield
  #     }
  #     catch {close $sock}
  #     chan flush $chan
  # }]
  method TransferComplete args {
    my log TransferComplete
    set chan {}
    foreach c $args {
      catch {chan event $c readable {}}
      catch {chan event $c writable {}}
      catch {chan flush $c}
      catch {chan close $c}
    }
    my destroy
  }

  # Appends the value of [arg string] to the end of [arg reply_body], as well as a trailing newline
  # character.
  method puts line {
    my variable reply_body
    append reply_body $line \n
  }








<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







440
441
442
443
444
445
446

































447
448
449
450
451
452
453
    }
    return $postdata
  }

  # Manage session data
  method Session_Load {} {}


































  # Appends the value of [arg string] to the end of [arg reply_body], as well as a trailing newline
  # character.
  method puts line {
    my variable reply_body
    append reply_body $line \n
  }

Changes to modules/httpd/build/scgi.tcl.

117
118
119
120
121
122
123

124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
    my counter url_hit
    try {
      # Read the SCGI request on byte at a time until we reach a ":"
      dict set query http HTTP_HOST {}
      dict set query http CONTENT_LENGTH 0
      dict set query http REQUEST_URI /
      dict set query http REMOTE_ADDR $ip

      set size {}
      while 1 {
        set char [::coroutine::util::read $sock 1]
        if {[chan eof $sock]} {
          catch {close $sock}
          return
        }
        if {$char eq ":"} break
        append size $char
      }
      # With length in hand, read the netstring encoded headers
      set inbuffer [::coroutine::util::read $sock [expr {$size+1}]]
      chan configure $sock -blocking 0 -buffersize 4096 -buffering full
      foreach {f v} [lrange [split [string range $inbuffer 0 end-1] \0] 0 end-1] {
        dict set query http $f $v
      }
      if {![dict exists $query http REQUEST_PATH]} {
        set uri [dict get $query http REQUEST_URI]
        set uriinfo [::uri::split $uri]
        dict set query http REQUEST_PATH    [dict get $uriinfo path]







>












|







117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
    my counter url_hit
    try {
      # Read the SCGI request on byte at a time until we reach a ":"
      dict set query http HTTP_HOST {}
      dict set query http CONTENT_LENGTH 0
      dict set query http REQUEST_URI /
      dict set query http REMOTE_ADDR $ip
      dict set query http DOCUMENT_ROOT [my clay get server/ doc_root]
      set size {}
      while 1 {
        set char [::coroutine::util::read $sock 1]
        if {[chan eof $sock]} {
          catch {close $sock}
          return
        }
        if {$char eq ":"} break
        append size $char
      }
      # With length in hand, read the netstring encoded headers
      set inbuffer [::coroutine::util::read $sock [expr {$size+1}]]
      chan configure $sock -translation {auto crlf} -blocking 0 -buffersize 4096 -buffering full
      foreach {f v} [lrange [split [string range $inbuffer 0 end-1] \0] 0 end-1] {
        dict set query http $f $v
      }
      if {![dict exists $query http REQUEST_PATH]} {
        set uri [dict get $query http REQUEST_URI]
        set uriinfo [::uri::split $uri]
        dict set query http REQUEST_PATH    [dict get $uriinfo path]
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
    try {
      set pageobj [::httpd::reply create ::httpd::object::$uuid [self]]
      dict set reply mixin protocol ::httpd::protocol.scgi
      $pageobj dispatch $sock $reply
    } on error {err errdat} {
      my debug [list ip: $ip error: $err errorinfo: [dict get $errdat -errorinfo]]
      my log BadRequest $uuid [list ip: $ip error: $err errorinfo: [dict get $errdat -errorinfo]]
      catch {$pageobj destroy}
      catch {chan event readable $sock {}}
      catch {chan event writeable $sock {}}
      catch {chan close $sock}
      return
    }
  }
}







|







162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
    try {
      set pageobj [::httpd::reply create ::httpd::object::$uuid [self]]
      dict set reply mixin protocol ::httpd::protocol.scgi
      $pageobj dispatch $sock $reply
    } on error {err errdat} {
      my debug [list ip: $ip error: $err errorinfo: [dict get $errdat -errorinfo]]
      my log BadRequest $uuid [list ip: $ip error: $err errorinfo: [dict get $errdat -errorinfo]]
      $pageobj clay refcount_decr
      catch {chan event readable $sock {}}
      catch {chan event writeable $sock {}}
      catch {chan close $sock}
      return
    }
  }
}

Changes to modules/httpd/build/server.tcl.

110
111
112
113
114
115
116

117
118
119
120
121
122
123
  # normal cases, an object of class [cmd ::http::reply] is created, and that class's
  # [cmd dispatch] method.
  # This action passes control of the socket to
  # the reply object. The reply object manages the rest of the transaction, including
  # closing the socket.
  ###
  method Connect {uuid sock ip} {

    yield [info coroutine]
    chan event $sock readable {}
    chan configure $sock \
      -blocking 0 \
      -translation {auto crlf} \
      -buffering line
    my counter url_hit







>







110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
  # normal cases, an object of class [cmd ::http::reply] is created, and that class's
  # [cmd dispatch] method.
  # This action passes control of the socket to
  # the reply object. The reply object manages the rest of the transaction, including
  # closing the socket.
  ###
  method Connect {uuid sock ip} {
    ::clay::cleanup
    yield [info coroutine]
    chan event $sock readable {}
    chan configure $sock \
      -blocking 0 \
      -translation {auto crlf} \
      -buffering line
    my counter url_hit
158
159
160
161
162
163
164
165
166
167

168
169
170
171
172
173
174
  # Check open connections for a time out event.
  ###
  method CheckTimeout {} {
    foreach obj [info commands ::httpd::object::*] {
      try {
        $obj timeOutCheck
      } on error {} {
        catch {$obj destroy}
      }
    }

  }

  method debug args {}

  ###
  # Given a key/value list of information, return a data structure describing how
  # the server should reply.







|


>







159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
  # Check open connections for a time out event.
  ###
  method CheckTimeout {} {
    foreach obj [info commands ::httpd::object::*] {
      try {
        $obj timeOutCheck
      } on error {} {
        $obj clay refcount_decr
      }
    }
    ::clay::cleanup
  }

  method debug args {}

  ###
  # Given a key/value list of information, return a data structure describing how
  # the server should reply.

Changes to modules/httpd/httpd.man.

226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252






253
254
255
256
257
258
259
260
261
262
263
264





265
266
267
268
269
270
271
 }

 }]



[para]
[class {Delegate}]
[list_begin definitions]
[call delegate [cmd <server>]]The server object which spawned this reply

[list_end]
[para]
[class {Variable}]
[list_begin definitions]
[call variable [cmd reply]]
 A dictionary which will converted into the MIME headers of the reply




[call variable [cmd request]]
 A dictionary containing the SCGI transformed HTTP headers for the request










[list_end]
[para]
[class {Methods}]
[list_begin definitions]
[call method [cmd "constructor"] [arg ServerObj] [opt "[arg args]"]]


[call method [cmd "destructor"] [opt "[arg dictargs]"]]

 clean up on exit









[call method [cmd "close"]]

 Close channels opened by this object









|

|

<
<
<
<












>
>
>
>
>
>












>
>
>
>
>







226
227
228
229
230
231
232
233
234
235
236




237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
 }

 }]



[para]
[class {Variable}]
[list_begin definitions]
[call variable [cmd ChannelRegister]]





[call variable [cmd reply]]
 A dictionary which will converted into the MIME headers of the reply




[call variable [cmd request]]
 A dictionary containing the SCGI transformed HTTP headers for the request




[list_end]
[para]
[class {Delegate}]
[list_begin definitions]
[call delegate [cmd <server>]]The server object which spawned this reply

[list_end]
[para]
[class {Methods}]
[list_begin definitions]
[call method [cmd "constructor"] [arg ServerObj] [opt "[arg args]"]]


[call method [cmd "destructor"] [opt "[arg dictargs]"]]

 clean up on exit




[call method [cmd "ChannelRegister"] [opt "[arg args]"]]
 Registers a channel to be closed by the close method



[call method [cmd "close"]]

 Close channels opened by this object


356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
 POST or PUSH. Returns an empty string otherwise.



[call method [cmd "Session_Load"]]
 Manage session data



[call method [cmd "TransferComplete"] [opt "[arg args]"]]
 Intended to be invoked from [cmd {chan copy}] as a callback. This closes every channel
 fed to it on the command line, and then destroys the object.

 [example {
     ###
     # Output the body
     ###
     chan configure $sock -translation binary -blocking 0 -buffering full -buffersize 4096
     chan configure $chan -translation binary -blocking 0 -buffering full -buffersize 4096
     if {$length} {
       ###
       # Send any POST/PUT/etc content
       ###
       chan copy $sock $chan -size $SIZE -command [info coroutine]
       yield
     }
     catch {close $sock}
     chan flush $chan
 }]



[call method [cmd "puts"] [arg line]]
 Appends the value of [arg string] to the end of [arg reply_body], as well as a trailing newline
 character.









<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







363
364
365
366
367
368
369























370
371
372
373
374
375
376
 POST or PUSH. Returns an empty string otherwise.



[call method [cmd "Session_Load"]]
 Manage session data


























[call method [cmd "puts"] [arg line]]
 Appends the value of [arg string] to the end of [arg reply_body], as well as a trailing newline
 character.


Changes to modules/httpd/httpd.tcl.

26
27
28
29
30
31
32


33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68



69
70
71
72
73
74
75
}
namespace eval ::httpd {
}
namespace eval ::scgi {
}
clay::define ::httpd::mime {
  method ChannelCopy {in out args} {


    set chunk 4096
    set size -1
    foreach {f v} $args {
      set [string trim $f -] $v
    }
    dict set info coroutine [info coroutine]
    if {$size>0 && $chunk>$size} {
        set chunk $size
    }
    set bytes 0
    set sofar 0
    set method [self method]
    while 1 {
      set command {}
      set error {}
      if {$size>=0} {
        incr sofar $bytes
        set remaining [expr {$size-$sofar}]
        if {$remaining <= 0} {
          break
        } elseif {$chunk > $remaining} {
          set chunk $remaining
        }
      }
      lassign [yieldto chan copy $in $out -size $chunk \
        -command [list [info coroutine] $method]] \
        command bytes error
      if {$command ne $method} {
        error "Subroutine $method interrupted"
      }
      if {[string length $error]} {
        error $error
      }
      if {[chan eof $in]} {
        break
      }



    }
  }
  method html_header {{title {}} args} {
    set result {}
    append result "<!DOCTYPE html>\n<HTML><HEAD>"
    if {$title ne {}} {
      append result "<TITLE>$title</TITLE>"







>
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
}
namespace eval ::httpd {
}
namespace eval ::scgi {
}
clay::define ::httpd::mime {
  method ChannelCopy {in out args} {
    try {
      my clay refcount_incr
      set chunk 4096
      set size -1
      foreach {f v} $args {
        set [string trim $f -] $v
      }
      dict set info coroutine [info coroutine]
      if {$size>0 && $chunk>$size} {
          set chunk $size
      }
      set bytes 0
      set sofar 0
      set method [self method]
      while 1 {
        set command {}
        set error {}
        if {$size>=0} {
          incr sofar $bytes
          set remaining [expr {$size-$sofar}]
          if {$remaining <= 0} {
            break
          } elseif {$chunk > $remaining} {
            set chunk $remaining
          }
        }
        lassign [yieldto chan copy $in $out -size $chunk \
          -command [list [info coroutine] $method]] \
          command bytes error
        if {$command ne $method} {
          error "Subroutine $method interrupted"
        }
        if {[string length $error]} {
          error $error
        }
        if {[chan eof $in]} {
          break
        }
      }
    } finally {
      my clay refcount_decr
    }
  }
  method html_header {{title {}} args} {
    set result {}
    append result "<!DOCTYPE html>\n<HTML><HEAD>"
    if {$title ne {}} {
      append result "<TITLE>$title</TITLE>"
274
275
276
277
278
279
280

281
282
283
284
285
286
287
288

289
290
291
292
293
294
295
296
297
298
299

300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326











327
328
329



330
331
332
333
334
335

336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351

352

353
354
355
356
357
358
359
          lappend pathlist $part
        }
      }
    }
    return $pathlist
  }
  method wait {mode sock} {

    if {[info coroutine] eq {}} {
      chan event $sock $mode [list set ::httpd::lock_$sock $mode]
      vwait ::httpd::lock_$sock
    } else {
      chan event $sock $mode [info coroutine]
      yield
    }
    chan event $sock $mode {}

  }
}

###
# END: core.tcl
###
###
# START: reply.tcl
###
::clay::define ::httpd::reply {
  superclass ::httpd::mime

  Delegate <server> {
    description {The server object which spawned this reply}
  }
  Dict reply {}
  Dict request {
    CONTENT_LENGTH 0
    COOKIE {}
    HTTP_HOST {}
    REFERER {}
    REQUEST_URI {}
    REMOTE_ADDR {}
    REMOTE_HOST {}
    USER_AGENT {}
    SESSION {}
  }
  constructor {ServerObj args} {
    my variable chan dispatched_time uuid
    set uuid [namespace tail [self]]
    set dispatched_time [clock milliseconds]
    my clay delegate <server> $ServerObj
    foreach {field value} [::clay::args_to_options {*}$args] {
      my clay set config $field: $value
    }
  }
  destructor {
    my close
  }











  method close {} {
    my variable chan
    if {[info exists chan] && $chan ne {}} {



      catch {chan event $chan readable {}}
      catch {chan event $chan writable {}}
      catch {chan flush $chan}
      catch {chan close $chan}
      set chan {}
    }

  }
  method Log_Dispatched {} {
    my log Dispatched [dict create \
     REMOTE_ADDR [my request get REMOTE_ADDR] \
     REMOTE_HOST [my request get REMOTE_HOST] \
     COOKIE [my request get HTTP_COOKIE] \
     REFERER [my request get HTTP_REFERER] \
     USER_AGENT [my request get HTTP_USER_AGENT] \
     REQUEST_URI [my request get REQUEST_URI] \
     HTTP_HOST [my request get HTTP_HOST] \
     SESSION [my request get SESSION] \
    ]
  }
  method dispatch {newsock datastate} {
    my variable chan request
    try {

      set chan $newsock

      chan event $chan readable {}
      chan configure $chan -translation {auto crlf} -buffering line
      if {[dict exists $datastate mixin]} {
        set mixinmap [dict get $datastate mixin]
      } else {
        set mixinmap {}
      }







>








>











>
















|










>
>
>
>
>
>
>
>
>
>
>

|
|
>
>
>
|
|
|
|
<

>
















>

>







279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355

356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
          lappend pathlist $part
        }
      }
    }
    return $pathlist
  }
  method wait {mode sock} {
    my clay refcount_incr
    if {[info coroutine] eq {}} {
      chan event $sock $mode [list set ::httpd::lock_$sock $mode]
      vwait ::httpd::lock_$sock
    } else {
      chan event $sock $mode [info coroutine]
      yield
    }
    chan event $sock $mode {}
    my clay refcount_decr
  }
}

###
# END: core.tcl
###
###
# START: reply.tcl
###
::clay::define ::httpd::reply {
  superclass ::httpd::mime
  Variable ChannelRegister {}
  Delegate <server> {
    description {The server object which spawned this reply}
  }
  Dict reply {}
  Dict request {
    CONTENT_LENGTH 0
    COOKIE {}
    HTTP_HOST {}
    REFERER {}
    REQUEST_URI {}
    REMOTE_ADDR {}
    REMOTE_HOST {}
    USER_AGENT {}
    SESSION {}
  }
  constructor {ServerObj args} {
    my variable dispatched_time uuid
    set uuid [namespace tail [self]]
    set dispatched_time [clock milliseconds]
    my clay delegate <server> $ServerObj
    foreach {field value} [::clay::args_to_options {*}$args] {
      my clay set config $field: $value
    }
  }
  destructor {
    my close
  }
  method ChannelRegister args {
    my variable ChannelRegister
    if {![info exists ChannelRegister]} {
      set ChannelRegister {}
    }
    foreach c $args {
      if {$c ni $ChannelRegister} {
        lappend ChannelRegister $c
      }
    }
  }
  method close {} {
    my variable ChannelRegister
    if {![info exists ChannelRegister]} {
      return
    }
    foreach c $ChannelRegister {
      catch {chan event $c readable {}}
      catch {chan event $c writable {}}
      catch {chan flush $c}
      catch {chan close $c}

    }
    set ChannelRegister {}
  }
  method Log_Dispatched {} {
    my log Dispatched [dict create \
     REMOTE_ADDR [my request get REMOTE_ADDR] \
     REMOTE_HOST [my request get REMOTE_HOST] \
     COOKIE [my request get HTTP_COOKIE] \
     REFERER [my request get HTTP_REFERER] \
     USER_AGENT [my request get HTTP_USER_AGENT] \
     REQUEST_URI [my request get REQUEST_URI] \
     HTTP_HOST [my request get HTTP_HOST] \
     SESSION [my request get SESSION] \
    ]
  }
  method dispatch {newsock datastate} {
    my variable chan request
    try {
      my clay refcount_incr
      set chan $newsock
      my ChannelRegister $chan
      chan event $chan readable {}
      chan configure $chan -translation {auto crlf} -buffering line
      if {[dict exists $datastate mixin]} {
        set mixinmap [dict get $datastate mixin]
      } else {
        set mixinmap {}
      }
381
382
383
384
385
386
387



388
389
390
391
392
393
394
      }
      my Session_Load
      my Log_Dispatched
      my Dispatch
    } on error {err errdat} {
      my error 500 $err [dict get $errdat -errorinfo]
      my DoOutput



    }
  }
  method Dispatch {} {
    # Invoke the URL implementation.
    my content
    my DoOutput
  }







>
>
>







405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
      }
      my Session_Load
      my Log_Dispatched
      my Dispatch
    } on error {err errdat} {
      my error 500 $err [dict get $errdat -errorinfo]
      my DoOutput
    } finally {
      my close
      my clay refcount_decr
    }
  }
  method Dispatch {} {
    # Invoke the URL implementation.
    my content
    my DoOutput
  }
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
        append result $reply_body
      } else {
        append result [my reply output]
      }
      chan puts -nonewline $chan $result
      my log HttpAccess {}
    }
    my destroy
  }
  method FormData {} {
    my variable chan formdata
    # Run this only once
    if {[info exists formdata]} {
      return $formdata
    }







<







500
501
502
503
504
505
506

507
508
509
510
511
512
513
        append result $reply_body
      } else {
        append result [my reply output]
      }
      chan puts -nonewline $chan $result
      my log HttpAccess {}
    }

  }
  method FormData {} {
    my variable chan formdata
    # Run this only once
    if {[info exists formdata]} {
      return $formdata
    }
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
      my variable chan
      chan configure $chan -translation binary -blocking 0 -buffering full -buffersize 4096
      set postdata [::coroutine::util::read $chan $length]
    }
    return $postdata
  }
  method Session_Load {} {}
  method TransferComplete args {
    my log TransferComplete
    set chan {}
    foreach c $args {
      catch {chan event $c readable {}}
      catch {chan event $c writable {}}
      catch {chan flush $c}
      catch {chan close $c}
    }
    my destroy
  }
  method puts line {
    my variable reply_body
    append reply_body $line \n
  }
  method RequestFind {field} {
    my variable request
    if {[dict exists $request $field]} {







<
<
<
<
<
<
<
<
<
<
<







568
569
570
571
572
573
574











575
576
577
578
579
580
581
      my variable chan
      chan configure $chan -translation binary -blocking 0 -buffering full -buffersize 4096
      set postdata [::coroutine::util::read $chan $length]
    }
    return $postdata
  }
  method Session_Load {} {}











  method puts line {
    my variable reply_body
    append reply_body $line \n
  }
  method RequestFind {field} {
    my variable request
    if {[dict exists $request $field]} {
782
783
784
785
786
787
788

789
790
791
792
793
794
795
    dict set result SERVER_SOFTWARE [my clay get server/ string]
    if {[string match 127.* $ip]} {
      dict set result LOCALHOST [expr {[lindex [split [dict getnull $result HTTP_HOST] :] 0] eq "localhost"}]
    }
    return $result
  }
  method Connect {uuid sock ip} {

    yield [info coroutine]
    chan event $sock readable {}
    chan configure $sock \
      -blocking 0 \
      -translation {auto crlf} \
      -buffering line
    my counter url_hit







>







797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
    dict set result SERVER_SOFTWARE [my clay get server/ string]
    if {[string match 127.* $ip]} {
      dict set result LOCALHOST [expr {[lindex [split [dict getnull $result HTTP_HOST] :] 0] eq "localhost"}]
    }
    return $result
  }
  method Connect {uuid sock ip} {
    ::clay::cleanup
    yield [info coroutine]
    chan event $sock readable {}
    chan configure $sock \
      -blocking 0 \
      -translation {auto crlf} \
      -buffering line
    my counter url_hit
824
825
826
827
828
829
830
831
832
833

834
835
836
837
838
839
840
    incr counters($which)
  }
  method CheckTimeout {} {
    foreach obj [info commands ::httpd::object::*] {
      try {
        $obj timeOutCheck
      } on error {} {
        catch {$obj destroy}
      }
    }

  }
  method debug args {}
  method dispatch {data} {
    set reply [my Dispatch_Local $data]
    if {[dict size $reply]} {
      return $reply
    }







|


>







840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
    incr counters($which)
  }
  method CheckTimeout {} {
    foreach obj [info commands ::httpd::object::*] {
      try {
        $obj timeOutCheck
      } on error {} {
        $obj clay refcount_decr
      }
    }
    ::clay::cleanup
  }
  method debug args {}
  method dispatch {data} {
    set reply [my Dispatch_Local $data]
    if {[dict size $reply]} {
      return $reply
    }
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
    set HTTP_STATUS [my reply get Status]
    my puts [subst $msg]
  }
}
::clay::define ::httpd::content.cache {
  method Dispatch {} {
    my variable chan
    try {
      my wait writable $chan
      chan configure $chan  -translation {binary binary}
      chan puts -nonewline $chan [my clay get cache/ data]
    } on error {err info} {
      my <server> debug [dict get $info -errorinfo]
    } finally {
      my TransferComplete $chan
    }
  }
}
::clay::define ::httpd::content.template {
  method content {} {
    if {[my request get HTTP_STATUS] ne {}} {
      my reply set Status [my request get HTTP_STATUS]
    }







<
|
|
|
<
<
<
<
<







1105
1106
1107
1108
1109
1110
1111

1112
1113
1114





1115
1116
1117
1118
1119
1120
1121
    set HTTP_STATUS [my reply get Status]
    my puts [subst $msg]
  }
}
::clay::define ::httpd::content.cache {
  method Dispatch {} {
    my variable chan

    my wait writable $chan
    chan configure $chan  -translation {binary binary}
    chan puts -nonewline $chan [my clay get cache/ data]





  }
}
::clay::define ::httpd::content.template {
  method content {} {
    if {[my request get HTTP_STATUS] ne {}} {
      my reply set Status [my request get HTTP_STATUS]
    }
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255

1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
      tailcall my DoOutput
    }
    if {$chan eq {}} return
    my wait writable $chan
    if {![info exists reply_file]} {
      tailcall my DoOutput
    }
    try {
      chan configure $chan  -translation {binary binary}
      my log HttpAccess {}
      ###
      # Return a stream of data from a file
      ###
      set size [file size $reply_file]
      my reply set Content-Length $size
      append result [my reply output] \n
      chan puts -nonewline $chan $result
      set reply_chan [open $reply_file r]

      my log SendReply [list length $size]
      ###
      # Output the file contents. With no -size flag, channel will copy until EOF
      ###
      chan configure $reply_chan -translation {binary binary} -buffersize 4096 -buffering full -blocking 0
      my ChannelCopy $reply_chan $chan -chunk 4096
    } finally {
      my TransferComplete $reply_chan $chan
    }
  }
}

###
# END: file.tcl
###
###







<
|
|
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
<
<
<







1249
1250
1251
1252
1253
1254
1255

1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272



1273
1274
1275
1276
1277
1278
1279
      tailcall my DoOutput
    }
    if {$chan eq {}} return
    my wait writable $chan
    if {![info exists reply_file]} {
      tailcall my DoOutput
    }

    chan configure $chan  -translation {binary binary}
    my log HttpAccess {}
    ###
    # Return a stream of data from a file
    ###
    set size [file size $reply_file]
    my reply set Content-Length $size
    append result [my reply output] \n
    chan puts -nonewline $chan $result
    set reply_chan [open $reply_file r]
    my ChannelRegister $reply_chan
    my log SendReply [list length $size]
    ###
    # Output the file contents. With no -size flag, channel will copy until EOF
    ###
    chan configure $reply_chan -translation {binary binary} -buffersize 4096 -buffering full -blocking 0
    my ChannelCopy $reply_chan $chan -chunk 4096



  }
}

###
# END: file.tcl
###
###
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
    if {$sock eq {}} {
      my error 404 {Not Found}
      tailcall my DoOutput
    }
    my log HttpAccess {}
    chan event $sock writable [info coroutine]
    yield
    try {
      my ProxyRequest $chan $sock
      my ProxyReply   $sock $chan
    } finally {
      my TransferComplete $chan $sock
    }
  }
}

###
# END: proxy.tcl
###
###







|
|
|
<
<
<







1425
1426
1427
1428
1429
1430
1431
1432
1433
1434



1435
1436
1437
1438
1439
1440
1441
    if {$sock eq {}} {
      my error 404 {Not Found}
      tailcall my DoOutput
    }
    my log HttpAccess {}
    chan event $sock writable [info coroutine]
    yield
    my ChannelRegister $sock
    my ProxyRequest $chan $sock
    my ProxyReply   $sock $chan



  }
}

###
# END: proxy.tcl
###
###
1525
1526
1527
1528
1529
1530
1531

1532
1533
1534
1535
1536
1537
1538
      ###
      # Send any POST/PUT/etc content
      ###
      my ChannelCopy $chana $chanb -size $length
    } else {
      chan flush $chanb
    }

    chan event $chanb readable [info coroutine]
    yield
  }
  method ProxyReply {chana chanb args} {
    my log ProxyReply [list args $args]
    chan event $chana readable {}
    set replyhead [my HttpHeaders $chana]







>







1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
      ###
      # Send any POST/PUT/etc content
      ###
      my ChannelCopy $chana $chanb -size $length
    } else {
      chan flush $chanb
    }
    my clay refcount_incr
    chan event $chanb readable [info coroutine]
    yield
  }
  method ProxyReply {chana chanb args} {
    my log ProxyReply [list args $args]
    chan event $chana readable {}
    set replyhead [my HttpHeaders $chana]
1553
1554
1555
1556
1557
1558
1559

1560
1561
1562
1563
1564
1565
1566
    chan puts $chanb $replybuffer
    ###
    # Output the body. With no -size flag, channel will copy until EOF
    ###
    chan configure $chana -translation binary -blocking 0 -buffering full -buffersize 4096
    chan configure $chanb -translation binary -blocking 0 -buffering full -buffersize 4096
    my ChannelCopy $chana $chanb -chunk 4096

  }
  method DirectoryListing {local_file} {
    my error 403 {Not Allowed}
    tailcall my DoOutput
  }
}








>







1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
    chan puts $chanb $replybuffer
    ###
    # Output the body. With no -size flag, channel will copy until EOF
    ###
    chan configure $chana -translation binary -blocking 0 -buffering full -buffersize 4096
    chan configure $chanb -translation binary -blocking 0 -buffering full -buffersize 4096
    my ChannelCopy $chana $chanb -chunk 4096
    my clay refcount_decr
  }
  method DirectoryListing {local_file} {
    my error 403 {Not Allowed}
    tailcall my DoOutput
  }
}

1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
          return
        }
        if {$char eq ":"} break
        append size $char
      }
      # With length in hand, read the netstring encoded headers
      set inbuffer [::coroutine::util::read $sock [expr {$size+1}]]
      chan configure $sock -blocking 0 -buffersize 4096 -buffering full
      foreach {f v} [lrange [split [string range $inbuffer 0 end-1] \0] 0 end-1] {
        dict set query http $f $v
      }
      if {![dict exists $query http REQUEST_PATH]} {
        set uri [dict get $query http REQUEST_URI]
        set uriinfo [::uri::split $uri]
        dict set query http REQUEST_PATH    [dict get $uriinfo path]







|







1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
          return
        }
        if {$char eq ":"} break
        append size $char
      }
      # With length in hand, read the netstring encoded headers
      set inbuffer [::coroutine::util::read $sock [expr {$size+1}]]
      chan configure $sock -translation {auto crlf} -blocking 0 -buffersize 4096 -buffering full
      foreach {f v} [lrange [split [string range $inbuffer 0 end-1] \0] 0 end-1] {
        dict set query http $f $v
      }
      if {![dict exists $query http REQUEST_PATH]} {
        set uri [dict get $query http REQUEST_URI]
        set uriinfo [::uri::split $uri]
        dict set query http REQUEST_PATH    [dict get $uriinfo path]
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
    try {
      set pageobj [::httpd::reply create ::httpd::object::$uuid [self]]
      dict set reply mixin protocol ::httpd::protocol.scgi
      $pageobj dispatch $sock $reply
    } on error {err errdat} {
      my debug [list ip: $ip error: $err errorinfo: [dict get $errdat -errorinfo]]
      my log BadRequest $uuid [list ip: $ip error: $err errorinfo: [dict get $errdat -errorinfo]]
      catch {$pageobj destroy}
      catch {chan event readable $sock {}}
      catch {chan event writeable $sock {}}
      catch {chan close $sock}
      return
    }
  }
}







|







1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
    try {
      set pageobj [::httpd::reply create ::httpd::object::$uuid [self]]
      dict set reply mixin protocol ::httpd::protocol.scgi
      $pageobj dispatch $sock $reply
    } on error {err errdat} {
      my debug [list ip: $ip error: $err errorinfo: [dict get $errdat -errorinfo]]
      my log BadRequest $uuid [list ip: $ip error: $err errorinfo: [dict get $errdat -errorinfo]]
      $pageobj clay refcount_decr
      catch {chan event readable $sock {}}
      catch {chan event writeable $sock {}}
      catch {chan close $sock}
      return
    }
  }
}
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
    }
    $pageobj clay mixinmap {*}$mixinmap
    if {[dict exists $reply delegate]} {
      $pageobj clay delegate {*}[dict get $reply delegate]
    }
    $pageobj dispatch $sock $reply
    set output [$pageobj output]
    catch {$pageobj destroy}
    return $output
  }
}

###
# END: plugin.tcl
###

    namespace eval ::httpd {
	namespace export *
    }








|












1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
    }
    $pageobj clay mixinmap {*}$mixinmap
    if {[dict exists $reply delegate]} {
      $pageobj clay delegate {*}[dict get $reply delegate]
    }
    $pageobj dispatch $sock $reply
    set output [$pageobj output]
    $pageobj clay refcount_decr
    return $output
  }
}

###
# END: plugin.tcl
###

    namespace eval ::httpd {
	namespace export *
    }

Changes to modules/httpd/httpd.test.

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61







62






























































































63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121


122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178

179
180
181
182

183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231




232
233
234
235
236
237
238
239
240

241
242
243
244
245
246
247

248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331

332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360

361











362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437




438
439
440
441

442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510

511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535

536
537
538
539
540
541
542
543

544

545
546
547
548

549
550
551

552


553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573

574
575
576
577
578
579
580
581
582
583
584
585
586

587
588
589
590
591
592
593

594

595
596
597
598
599
600

601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664

665


666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687


688
689
690
691
692
693
694
695
696
697
698
699
700
} else {
  set TCLLIBMOD [file join $MODDIR .. .. tcllib modules]
}
source [file join $TCLLIBMOD devtools testutilities.tcl]

testsNeedTcl     8.6 ;# tool requires 8.6
testsNeedTcltest 2

testsNeed TclOO 1

support {
  use [file join ${TCLLIBMOD} fumagic rtcore.tcl]       fileutil::magic::rt
  use [file join ${TCLLIBMOD} fumagic filetypes.tcl]    fileutil::magic::filetype
  use [file join ${TCLLIBMOD} textutil string.tcl]      textutil::string
  use [file join ${TCLLIBMOD} textutil repeat.tcl]      textutil::repeat
  use [file join ${TCLLIBMOD} textutil tabify.tcl]      textutil::tabify
  use [file join ${TCLLIBMOD} markdown markdown.tcl]    Markdown
  use [file join ${TCLLIBMOD} ncgi ncgi.tcl]            ncgi
  use [file join ${TCLLIBMOD} log logger.tcl]           logger
  use [file join ${TCLLIBMOD} base64 base64.tcl]        base64
  use [file join ${TCLLIBMOD} md5 md5x.tcl]             md5
  use [file join ${TCLLIBMOD} mime mime.tcl]            mime
  use [file join ${TCLLIBMOD} uuid uuid.tcl]            uuid
  use [file join ${TCLLIBMOD} cmdline cmdline.tcl]      cmdline
  use [file join ${TCLLIBMOD} fileutil fileutil.tcl]    fileutil
  use [file join ${TCLLIBMOD} sha1 sha1.tcl]            sha1
  use [file join ${TCLLIBMOD} uri uri.tcl]              uri
  use [file join ${TCLLIBMOD} ncgi ncgi.tcl]            ncgi
  use [file join ${TCLLIBMOD} dns ip.tcl]               ip
  use [file join ${TCLLIBMOD} nettool nettool.tcl]      nettool
  use [file join ${TCLLIBMOD} coroutine coroutine.tcl]  coroutine
  use [file join ${TCLLIBMOD} dicttool dicttool.tcl]    dicttool
  use [file join ${TCLLIBMOD} cron cron.tcl]            cron
  use [file join ${TCLLIBMOD} virtchannel_core core.tcl] tcl::chan::core
  use [file join ${TCLLIBMOD} virtchannel_core events.tcl] tcl::chan::events
  use [file join ${TCLLIBMOD} virtchannel_base memchan.tcl] tcl::chan::memchan
  use [file join ${TCLLIBMOD} websocket websocket.tcl]  websocket
  use [file join ${MODDIR} clay clay.tcl]               clay
}

testing {
  useLocal httpd.tcl httpd
}

# Set to true for debugging and traces
set ::DEBUG 0
set ::clay::debug $::DEBUG

proc DEBUG args {
  if {$::DEBUG} {
    uplevel 1 $args
  }
}






































































































# -------------------------------------------------------------------------

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!)
proc ::httpd::test::compare {actual correct} {
	set result {}
	set cbuf [split $correct \n]
	set abuf [split $actual \n]
	for {set i 0} {$i < [llength $cbuf]} {incr i} {
		set cline [string trim [lindex $cbuf $i]]
		set aline [string trim [lindex $abuf $i]]
		if {![string match $cline $aline]} {
			if {$cline ne $aline} {
				append result "Line $i differs [list $aline] != [list $cline]" \n
			}
		}
	}
  if {[llength $result]} {
  	puts [list ACTUAL $actual]
	  puts [list CORRECT $correct]
  }
	return $result
}

proc ::httpd::test::send {port http headers body} {
  set sock [socket localhost $port]
  variable reply
  set reply($sock) {}
  chan configure $sock -translation {crlf crlf} -blocking 0 -buffering full -buffersize 4096
  chan event $sock readable [list ::httpd::test::get_reply $sock]

	puts $sock $http
	if {![dict exists $headers Host]} {
	  dict set headers Host localhost
	}
	if {[string length $body]} {
		if {![dict exists $headers Content-Type]} {
			dict set headers Content_Type text/plain
		}
		dict set headers Content-Length [string length $body]
	}
	foreach {f v} $headers {
		puts $sock "${f}: $v"
	}
  puts $sock {}
	if {[string length $body]} {
    chan configure $sock -translation binary -blocking 0 -buffering full -buffersize 4096
		puts -nonewline $sock $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)
  }
}


clay::define ::httpd::server {
  method log args {}

  method TemplateSearch page {
    set doc_root [my clay get server/ doc_root]
    if {$doc_root ne {} && [file exists [file join $doc_root $page.tml]]} {
      return [::fileutil::cat [file join $doc_root $page.tml]]
    }
    if {$doc_root ne {} && [file exists [file join $doc_root $page.html]]} {
      return [::fileutil::cat [file join $doc_root $page.html]]
    }
    switch $page {
      redirect {
        return {300 Redirect}
      }
      notfound {
        return {404 Not Found}
      }
      internal_error {
        return {500 Server Internal Error}
      }
    }
  }


  ::DEBUG method debug args {
    puts stderr $args
  }

  ::DEBUG method log args {
    puts stdout $args
  }
}


###
# Modify the reply class to return plain text
###
clay::define ::httpd::reply {

  method HttpHeaders_Default {} {
    return {Status {200 OK}

    Content-Type {text/plain}
		Connection close}
  }


  method reset {} {
    my variable reply_body
    my reply replace [my HttpHeaders_Default]
    set reply_body {}
  }

  method error {code {msg {}} {errorInfo {}}} {
    my clay set HTTP_ERROR $code
    my reset
    set errorstring [my http_code_string $code]
    set qheaders [my clay dump]
    dict with qheaders {}
    my reply replace {}
    my reply set Status "$code $errorstring"
    my reply set Content-Type text/plain
    my puts "$code $errorstring"
  }
}

clay::define ::test::content.echo {
	method content {} {
		my variable reply_body
		set reply_body [my PostData [my request get CONTENT_LENGTH]]
		#puts [list REPLY BODY WAS $reply_body]
	}
}
clay::define ::test::content.file {
	superclass ::httpd::content.file
	method content {} {
	  my reset
    set doc_root [my request get DOCUMENT_ROOT]
    my variable reply_file
    set reply_file [file join $doc_root pkgIndex.tcl]
	}
}
clay::define ::test::content.time {
	method content {} {
		my variable reply_body
		set reply_body [clock seconds]
	}
}
clay::define ::test::content.error {
	method content {} {
		error {The programmer asked me to die this way}
	}
}
clay::define ::test::content.cgi {
	superclass ::httpd::content.cgi





}

clay::define ::httpd::test::reply {
	superclass ::httpd::reply ::test::content.echo
}

###
# Build the server
###

::httpd::server create TESTAPP port 10001 doc_root $::TESTDIR
TESTAPP plugin dict_dispatch
TESTAPP uri add * /     [list mixin {reply ::test::content.echo}]
TESTAPP uri add * /echo [list mixin {reply ::test::content.echo}]
TESTAPP uri add * /file [list mixin {reply ::test::content.file}]
TESTAPP uri add * /time [list mixin {reply ::test::content.time}]
TESTAPP uri add * /error [list mixin {replyy ::test::content.error}]


# Catch all
#TESTAPP uri add * * [list mixin {reply httpd::content.echo}]

::DEBUG puts httpd-client-0001
test httpd-client-0001 {Do an echo request} {

set reply [::httpd::test::send 10001 {POST /echo HTTP/1.0} {} {THIS IS MY CODE}]
::httpd::test::compare $reply {HTTP/1.0 200 OK
Content-Type: text/plain
Connection: close
Content-Length: *

THIS IS MY CODE}
} {}

::DEBUG puts httpd-client-0002
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}]
::httpd::test::compare $reply {HTTP/1.0 200 OK
Content-Type: text/plain
Connection: close
Content-Length: 29

THOUGH THERE ARE MANY LIKE IT}
} {}

::DEBUG puts httpd-client-0003
test httpd-client-0003 {Do another echo request} {
set reply [::httpd::test::send 10001 {POST /echo HTTP/1.0} {} {THIS ONE ALONE IS MINE}]
::httpd::test::compare $reply {HTTP/1.0 200 OK
Content-Type: text/plain
Connection: close
Content-Length: *

THIS ONE ALONE IS MINE}
}  {}

::DEBUG puts httpd-client-0004
test httpd-client-0004 {URL Generates Error} {

set reply [::httpd::test::send 10001 {POST /error HTTP/1.0} {} {THIS ONE ALONE IS MINE}]

::httpd::test::compare $reply {HTTP/1.0 500 Server Internal Error
Content-Type: text/plain
Connection: close
Content-Length: *

500 Server Internal Error}
} {}

set checkreply [subst {HTTP/1.0 200 OK
Content-Type: text/plain
Connection: close
Content-Length: *

[clock seconds]}]

::DEBUG puts httpd-client-0005
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}]
::httpd::test::compare $reply $checkreply
} {}

set fin [open [file join $TESTDIR pkgIndex.tcl] r]
set replyfile [read $fin]
close $fin
set checkreply "HTTP/1.0 200 OK
Content-Type: text/plain
Connection: close
Content-Length: [string length $replyfile]

$replyfile"

::DEBUG puts httpd-client-0006
test httpd-client-0006 {Return a file} {
set reply [::httpd::test::send 10001 {GET /file HTTP/1.0} {} {}]
::httpd::test::compare $reply $checkreply
} {}

::DEBUG puts httpd-client-0007
test httpd-client-0007 {URL Generates Not Found} {

set reply [::httpd::test::send 10001 {POST /doesnotexist HTTP/1.0} {} {THIS ONE ALONE IS MINE}]


::httpd::test::compare $reply {HTTP/1.0 404 Not Found
Content-Type: text/plain
Connection: close
Content-Length: *

404 Not Found}
} {}

# -------------------------------------------------------------------------
# Test proxies

clay::define ::test::content.proxy {
	superclass ::httpd::content.proxy

  method proxy_channel {} {
    return [::socket localhost [my clay get proxy_port]]
  }
}


::httpd::server create TESTPROXY port 10002 doc_root $::TESTDIR
TESTAPP   uri add * /proxy*     [list mixin {reply ::test::content.proxy} proxy_port [TESTPROXY port_listening]]
TESTPROXY plugin dict_dispatch
TESTPROXY uri add * /     [list mixin {reply ::test::content.echo}]
TESTPROXY uri add * /echo [list mixin {reply ::test::content.echo}]
TESTPROXY uri add * /file [list mixin {reply ::test::content.file}]
TESTPROXY uri add * /time [list mixin {reply ::test::content.time}]
TESTPROXY uri add * /error [list mixin {reply ::test::content.error}]













::DEBUG puts httpd-proxy-0001
test httpd-proxy-0001 {Do an echo request} {

set reply [::httpd::test::send 10001 {POST /proxy/echo HTTP/1.0} {} {THIS IS MY CODE}]
::httpd::test::compare $reply {HTTP/1.0 200 OK
Content-Type: text/plain
Connection: close
Content-Length: *

THIS IS MY CODE}
} {}

::DEBUG puts httpd-proxy-0002
test httpd-proxy-0002 {Do another echo request} {
set reply [::httpd::test::send 10001 {POST /proxy/echo HTTP/1.0} {} {THOUGH THERE ARE MANY LIKE IT}]
::httpd::test::compare $reply {HTTP/1.0 200 OK
Content-Type: text/plain
Connection: close
Content-Length: 29

THOUGH THERE ARE MANY LIKE IT}
} {}

::DEBUG puts httpd-proxy-0003
test httpd-proxy-0003 {Do another echo request} {
set reply [::httpd::test::send 10001 {POST /proxy/echo HTTP/1.0} {} {THIS ONE ALONE IS MINE}]
::httpd::test::compare $reply {HTTP/1.0 200 OK
Content-Type: text/plain
Connection: close
Content-Length: *

THIS ONE ALONE IS MINE}
}  {}

::DEBUG puts httpd-proxy-0004
test httpd-proxy-0004 {URL Generates Error} {

set reply [::httpd::test::send 10001 {POST /proxy/error HTTP/1.0} {} {THIS ONE ALONE IS MINE}]

::httpd::test::compare $reply {HTTP/1.0 500 Server Internal Error
Content-Type: text/plain
Connection: close
Content-Length: *

500 Server Internal Error}
} {}

set checkreply [subst {HTTP/1.0 200 OK
Content-Type: text/plain
Connection: close
Content-Length: *

[clock seconds]}]

::DEBUG puts httpd-proxy-0005
test httpd-proxy-0005 {URL Different output with a different request} {
set reply [::httpd::test::send 10001 {POST /proxy/time HTTP/1.0} {} {THIS ONE ALONE IS MINE}]
::httpd::test::compare $reply $checkreply
} {}

set fin [open [file join $TESTDIR pkgIndex.tcl] r]
set replyfile [read $fin]
close $fin
set checkreply "HTTP/1.0 200 OK
Content-Type: text/plain
Connection: close
Content-Length: [string length $replyfile]

$replyfile"

::DEBUG puts httpd-proxy-0006
test httpd-proxy-0006 {Return a file} {
set reply [::httpd::test::send 10001 {GET /proxy/file HTTP/1.0} {} {}]
::httpd::test::compare $reply $checkreply
} {}





# -------------------------------------------------------------------------
# cgi
TESTAPP plugin local_memchan


TESTAPP uri add * /cgi-bin* [list mixin {reply ::test::content.cgi} path $::TESTDIR]

set fout [open [file join $TESTDIR test.tcl] w]
puts $fout {#!/usr/bin/tclsh

puts stdout "Status: 200 OK"
if {$::env(CONTENT_LENGTH) > 0} {
  puts stdout "Content-Type: $::env(CONTENT_TYPE)"
  set dat [read stdin $::env(CONTENT_LENGTH)]
} else {
  puts stdout "Content-Type: text/plain"
  set dat "Hi!"
}
puts stdout "Content-Length: [string length $dat]"
puts stdout {}
puts stdout $dat
exit 0
}
close $fout

::DEBUG puts httpd-cgi-0001
test httpd-cgi-0001 {CGI Post} {

set reply [::httpd::test::send 10001 {POST /cgi-bin/test.tcl HTTP/1.0} {} {THIS IS MY CODE}]
::httpd::test::compare $reply {HTTP/1.0 200 OK
Status: 200 OK
Content-Type: text/plain
Content-Length: *

THIS IS MY CODE}
} {}

::DEBUG puts httpd-cgi-0002
test httpd-cgi-0002 {CGI Get} {

set reply [::httpd::test::send 10001 {GET /cgi-bin/test.tcl HTTP/1.0} {} {}]
::httpd::test::compare $reply {HTTP/1.0 200 OK
Status: 200 OK
Content-Type: text/plain
Content-Length: *

Hi!}
} {}

###
# Test the local geturl method
###
set now [clock seconds]
set dat [TESTAPP local_memchan geturl /time]
test httpd-memchan-0001 {Memchan GET} {
  TESTAPP local_memchan geturl /time
} $now

# -------------------------------------------------------------------------
namespace eval ::scgi {}
namespace eval ::scgi::test {}

###
# Minimal test harness for the .tests
# Not intended for public consumption
# (But if you find it handy, please steal!)
namespace eval ::scgi::test {}

proc ::scgi::encode_request {headers body info} {
  variable server_block

  dict set outdict CONTENT_LENGTH [string length $body]
  set outdict [dict merge $outdict $server_block $info]
  dict set outdict PWD [pwd]

  foreach {key value} $headers {
    if {$key in {
      DOCUMENT_ROOT
      HTTPS
      PATH
      REQUEST_METHOD REQUEST_URI
      REMOTE_HOST REMOTE_ADDR REMOTE_PORT
      SCRIPT_NAME
    } || [string range $key 0 5] eq "HTTP_"} {
      dict set outdict $key $value
    } else {
      dict set outdict HTTP_[string map {"-" "_"} [string toupper $key]] $value
    }
  }
  set result {}
  foreach {name value} $outdict {
    append result $name \x00 $value \x00
  }
  return "[string length $result]:$result,"
}

proc ::scgi::test::send {port headers body} {
  set sock [socket localhost $port]
  variable reply
  set reply($sock) {}

  if {![dict exists $headers HOST]} {
    dict set headers HOST localhost
  }
  dict set headers REMOTE_IP 127.0.0.1
  dict set headers REMOTE_HOST localhost

  chan configure $sock -translation binary -blocking 0 -buffering full -buffersize 4096
  chan event $sock readable [list ::scgi::test::get_reply $sock]

  set block [::scgi::encode_request $headers $body {}]

  puts -nonewline $sock $block
  flush $sock
  puts -nonewline $sock $body
  flush $sock

  while {$reply($sock) eq {}} {
    update
  }

  #vwait [namespace current]::reply($sock)


  return $reply($sock)
}

proc ::scgi::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)
  }
}

namespace eval ::scgi {
  variable server_block {SCGI 1.0 SERVER_SOFTWARE {TclScgiServer/0.1}}
}

###
# Build the reply class
###

::clay::define ::scgi::test::reply {
  superclass ::httpd::reply

  method reset {} {
    my variable reply_body
    my reply replace [my HttpHeaders_Default]
    set reply_body {}
  }
}

###
# Build the server
###

::clay::define scgi::test::app {
  superclass ::httpd::server.scgi

  clay set reply_class ::scgi::test::reply
}

puts [list ::test::content.file [info commands ::test::content.file]]

scgi::test::app create TESTSCGI port 10003 doc_root $::TESTDIR

TESTSCGI plugin dict_dispatch
TESTSCGI uri add * /     [list mixin {reply ::test::content.echo}]
TESTSCGI uri add * /echo [list mixin {reply ::test::content.echo}]
TESTSCGI uri add * /file [list mixin {reply ::test::content.file}]
TESTSCGI uri add * /time [list mixin {reply ::test::content.time}]
TESTSCGI uri add * /error [list mixin {reply ::test::content.error}]


::DEBUG puts scgi-client-0001
test scgi-client-0001 {Do an echo request} {

set reply [::scgi::test::send 10003 {REQUEST_METHOD POST REQUEST_URI /echo} {THIS IS MY CODE}]
set checkreply {Status: 200 OK
Content-Type: text/plain
Connection: close
Content-Length: *

THIS IS MY CODE}
::httpd::test::compare $reply $checkreply
} {}


::DEBUG puts scgi-client-0002
test scgi-client-0002 {Do another echo request} {
set reply [::scgi::test::send 10003 {REQUEST_METHOD POST REQUEST_URI /echo} {THOUGH THERE ARE MANY LIKE IT}]
set checkreply {Status: 200 OK
Content-Type: text/plain
Connection: close
Content-Length: *

THOUGH THERE ARE MANY LIKE IT}
::httpd::test::compare $reply $checkreply
} {}

::DEBUG puts scgi-client-0003
test scgi-client-0003 {Do another echo request} {
set reply [::scgi::test::send 10003 {REQUEST_METHOD POST REQUEST_URI /echo} {THIS ONE ALONE IS MINE}]
set checkreply {Status: 200 OK
Content-Type: text/plain
Connection: close
Content-Length: *

THIS ONE ALONE IS MINE}
::httpd::test::compare $reply $checkreply
} {}

::DEBUG puts scgi-client-0004
test scgi-client-0004 {URL Generates Error} {

set reply [::scgi::test::send 10003 {REQUEST_METHOD POST REQUEST_URI /error} {THIS ONE ALONE IS MINE}]

set checkreply {Status: 500 Server Internal Error
Content-Type: text/plain
Connection: close
Content-Length: *

500 Server Internal Error
}
::httpd::test::compare $reply $checkreply
} {}

set checkreply [subst {Status: 200 OK
Content-Type: text/plain
Connection: close
Content-Length: *

[clock seconds]}]

::DEBUG puts scgi-client-0005
test scgi-client-0005 {URL Different output with a different request} {
set reply [::scgi::test::send 10003 {REQUEST_METHOD POST REQUEST_URI /time} {THIS ONE ALONE IS MINE}]




::httpd::test::compare $reply $checkreply
} {}

set fin [open [file join $TESTDIR pkgIndex.tcl] r]
set checkfile [read $fin]
close $fin

###
# Nerfed: There is something screwy that is preventing this test from working
# properly in Sak. But only this test, and not the other two (normal client and proxy)
# who are doing essentially the same operation
# Investigate at some point - Sean
###
#::DEBUG puts scgi-client-0006
#test scgi-client-0006 {Return a file} {
#set reply [::scgi::test::send 10003 {REQUEST_METHOD GET REQUEST_URI /file} {}]

#set checkreply "Status: 200 OK
#Content-Type: text/plain
#Connection: close
#Content-Length: [string length $checkfile]



#$checkfile"
#::httpd::test::compare $reply $checkreply
#} {}

::DEBUG puts all-tests-finished
file delete [file join $TESTDIR test.tcl]
# -------------------------------------------------------------------------

testsuiteCleanup

# Local variables:
# mode: tcl
# indent-tabs-mode: nil







|



|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|



|







|

|
|
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
|



|
|
|
|
|
|
|
|

<


|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|


|
|
|

|
|
|








|
|
>
|
|
|
|
>
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|



|
|
|
|
|


|
|
|
|
|
|
|


|
|
|
|


|
|
|


|
|
>
>
>
>
|
|

|





>


|
|
|
|
|
>





|
<
|
<
<
<
<
<
|
<



|
<
<
<
<
<
|
<


|
|
<
<
<
<
<
|
<


|
<
|
|
<
<
<
<

<
<
<
<
<
<
<
<
<
<

|
|
<
<
|
<
<
<
<
<
<
<
<
<


|
|
<
|


|
<
|
>

<
<
<
|
|
<
|
|




|

|
|
|


<

|

|
|
|
|
|
>

>
>
>
>
>
>
>
>
>
>
>

|
<
|
<
<
<
<
<
|
<


|
|
<
<
<
<
<
|
<


|
|
<
<
<
<
<
|
<


|
<
|
|
<
<
<
<

<
<
<
<
<
<
<
<
<
<

|
|
<
<
|
<
<
<
<
<
<
<
<
<


|
|
<
|

>
>
>
>


<

>
|

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

|
<
|
<
<
<
<
<
|
|


|
<
|
<
<
<
<
|
|
<




<
<

|
|









<


|

|
|
|
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|



|
|
|
>
|
|
|
|
|

|
|
>
|
>
|
|
|
|
>
|
|
|
>
|
>
>
|



|
|
|
|
|
|
|
|









>

|

|
|
|
|
|





>

|

|


|
>

>

|
|
|
|

>


|
<
|
<
<
<
<
<
|
<
<

<

|
|
<
<
<
<
<
|
<
<


|
|
<
<
<
<
<
|
<
<


|
<
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


|
|
>

>
>
|
|

<
<
<

<
<
<
<
<
<
|
|
|
|
<
<
|
<
|
>
>
|
|
|


<







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
























169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213

214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339

340





341

342
343
344
345





346

347
348
349
350





351

352
353
354

355
356




357










358
359
360


361









362
363
364
365

366
367
368
369

370
371
372



373
374

375
376
377
378
379
380
381
382
383
384
385
386
387

388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410

411





412

413
414
415
416





417

418
419
420
421





422

423
424
425

426
427




428










429
430
431


432









433
434
435
436

437
438
439
440
441
442
443
444

445
446
447
448


















449
450

451





452
453
454
455
456

457




458
459

460
461
462
463


464
465
466
467
468
469
470
471
472
473
474
475

476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588

589





590


591

592
593
594





595


596
597
598
599





600


601
602
603

604
605
















606
607
608
609
610
611
612
613
614
615
616



617






618
619
620
621


622

623
624
625
626
627
628
629
630

631
632
633
634
635
636
637
} else {
  set TCLLIBMOD [file join $MODDIR .. .. tcllib modules]
}
source [file join $TCLLIBMOD devtools testutilities.tcl]

testsNeedTcl     8.6 ;# tool requires 8.6
testsNeedTcltest 2
set NOW [clock seconds]
testsNeed TclOO 1

support {
    use [file join ${TCLLIBMOD} fumagic rtcore.tcl]       fileutil::magic::rt
    use [file join ${TCLLIBMOD} fumagic filetypes.tcl]    fileutil::magic::filetype
    use [file join ${TCLLIBMOD} textutil string.tcl]      textutil::string
    use [file join ${TCLLIBMOD} textutil repeat.tcl]      textutil::repeat
    use [file join ${TCLLIBMOD} textutil tabify.tcl]      textutil::tabify
    use [file join ${TCLLIBMOD} markdown markdown.tcl]    Markdown
    use [file join ${TCLLIBMOD} ncgi ncgi.tcl]            ncgi
    use [file join ${TCLLIBMOD} log logger.tcl]           logger
    use [file join ${TCLLIBMOD} base64 base64.tcl]        base64
    use [file join ${TCLLIBMOD} md5 md5x.tcl]             md5
    use [file join ${TCLLIBMOD} mime mime.tcl]            mime
    use [file join ${TCLLIBMOD} uuid uuid.tcl]            uuid
    use [file join ${TCLLIBMOD} cmdline cmdline.tcl]      cmdline
    use [file join ${TCLLIBMOD} fileutil fileutil.tcl]    fileutil
    use [file join ${TCLLIBMOD} sha1 sha1.tcl]            sha1
    use [file join ${TCLLIBMOD} uri uri.tcl]              uri
    use [file join ${TCLLIBMOD} ncgi ncgi.tcl]            ncgi
    use [file join ${TCLLIBMOD} dns ip.tcl]               ip
    use [file join ${TCLLIBMOD} nettool nettool.tcl]      nettool
    use [file join ${TCLLIBMOD} coroutine coroutine.tcl]  coroutine
    use [file join ${TCLLIBMOD} dicttool dicttool.tcl]    dicttool
    use [file join ${TCLLIBMOD} cron cron.tcl]            cron
    use [file join ${TCLLIBMOD} virtchannel_core core.tcl] tcl::chan::core
    use [file join ${TCLLIBMOD} virtchannel_core events.tcl] tcl::chan::events
    use [file join ${TCLLIBMOD} virtchannel_base memchan.tcl] tcl::chan::memchan
    use [file join ${TCLLIBMOD} websocket websocket.tcl]  websocket
    use [file join ${MODDIR} clay clay.tcl]               clay
}

testing {
    useLocal httpd.tcl httpd
}

# Set to true for debugging and traces
set ::DEBUG 0
set ::clay::debug $::DEBUG

proc DEBUG args {
    if {!$::DEBUG} return
    uplevel 1 $args
}

# -------------------------------------------------------------------------
# Constructors for various expected replies.
proc IndexReply {{head {HTTP/1.0}}} {
   global TESTDIR
   set fin       [open [file join $TESTDIR pkgIndex.tcl] r]
   set replyfile [read $fin]
   close $fin

   append checkreply "$head 200 OK" \n
   append checkreply "Content-Type: text/plain" \n
   append checkreply "Connection: close" \n
   append checkreply "Content-Length: [string length $replyfile]" \n
   append checkreply \n
   append checkreply $replyfile

   return $checkreply
}

proc 404 {} {
    lappend map "        " ""
    lappend map "    "     ""
    # The map removes the indentation of the value
    return [string map $map {HTTP/1.0 404 Not Found
        Content-Type: text/plain
        Connection: close
        Content-Length: *

        404 Not Found
    }]
}

proc 200 {text {len *}} {
    lappend map "        " ""
    # The map removes the indentation of the value
    # and inserts the dynamic parts
    lappend map @C $text
    lappend map @L $len
    return [string map $map {HTTP/1.0 200 OK
        Content-Type: text/plain
        Connection: close
        Content-Length: @L

        @C}]
}

proc 200+status-head {text {len *}} {
    lappend map "        " ""
    # The map removes the indentation of the value
    # and inserts the dynamic parts
    lappend map @C $text
    lappend map @L $len
    return [string map $map {Status: 200 OK
        Content-Type: text/plain
        Connection: close
        Content-Length: @L

        @C}]
}

proc 200+status-conn {text {len *}} {
    lappend map "        " ""
    # The map removes the indentation of the value
    # and inserts the dynamic parts
    lappend map @C $text
    lappend map @L $len
    return [string map $map {HTTP/1.0 200 OK
        Status: 200 OK
        Content-Type: text/plain
        Content-Length: @L

        @C}]
}

proc 500 {} {
    lappend map "        " ""
    lappend map "    "     ""
    # The map removes the indentation of the value
    return [string map $map {HTTP/1.0 500 Server Internal Error
        Content-Type: text/plain
        Connection: close
        Content-Length: *

        500 Server Internal Error
    }]
}

proc 500+status-head {} {
    lappend map "        " ""
    lappend map "    "     ""
    # The map removes the indentation of the value
    return [string map $map {Status: 500 Server Internal Error
        Content-Type: text/plain
        Connection: close
        Content-Length: *

        500 Server Internal Error
    }]
}

# Likely a band aid, see AKU
proc norm-eol {x} { string map [list "\r\n" "\n"] $x }

# -------------------------------------------------------------------------

namespace eval ::httpd {}
namespace eval ::httpd::test {}

























proc ::httpd::test::send {port http headers body} {
    set sock [socket localhost $port]
    variable reply
    set reply($sock) {}
    chan configure $sock -translation {crlf crlf} -blocking 0 -buffering full -buffersize 4096
    chan event $sock readable [list ::httpd::test::get_reply $sock]

    puts $sock $http
    if {![dict exists $headers Host]} {
        dict set headers Host localhost
    }
    if {[string length $body]} {
        if {![dict exists $headers Content-Type]} {
            dict set headers Content_Type text/plain
        }
        dict set headers Content-Length [string length $body]
    }
    foreach {f v} $headers {
        puts $sock "${f}: $v"
    }
    puts $sock {}
    if {[string length $body]} {
        chan configure $sock -translation binary -blocking 0 -buffering full -buffersize 4096
        puts -nonewline $sock $body
    }
    flush $sock
    while {$reply($sock) eq {}} {
        update
    }
    #vwait [namespace current]::reply($sock)

    #puts ZZ\t[join [split $reply($sock) \n] "|\nZZ\t"]|
    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)
    }
}


clay::define ::httpd::server {
    method log args {}

    method TemplateSearch page {
        set doc_root [my clay get server/ doc_root]
        if {$doc_root ne {} && [file exists [file join $doc_root $page.tml]]} {
            return [::fileutil::cat [file join $doc_root $page.tml]]
        }
        if {$doc_root ne {} && [file exists [file join $doc_root $page.html]]} {
            return [::fileutil::cat [file join $doc_root $page.html]]
        }
        switch $page {
            redirect {
                return {300 Redirect}
            }
            notfound {
                return {404 Not Found}
            }
            internal_error {
                return {500 Server Internal Error}
            }
        }
    }


    ::DEBUG method debug args {
        puts stderr $args
    }

    ::DEBUG method log args {
        puts stdout $args
    }
}


###
# Modify the reply class to return plain text
###
clay::define ::httpd::reply {

    method HttpHeaders_Default {} {
        return {
            Status       {200 OK}
            Content-Type {text/plain}
            Connection   close
        }
    }

    method reset {} {
        my variable reply_body
        my reply replace [my HttpHeaders_Default]
        set reply_body {}
    }

    method error {code {msg {}} {errorInfo {}}} {
        my clay set HTTP_ERROR $code
        my reset
        set errorstring [my http_code_string $code]
        set qheaders [my clay dump]
        dict with qheaders {}
        my reply replace {}
        my reply set Status "$code $errorstring"
        my reply set Content-Type text/plain
        my puts "$code $errorstring"
    }
}

clay::define ::test::content.echo {
    method content {} {
        my variable reply_body
        set reply_body [my PostData [my request get CONTENT_LENGTH]]
        #puts [list REPLY BODY WAS $reply_body]
    }
}
clay::define ::test::content.file {
    superclass ::httpd::content.file
    method content {} {
        my reset
        set doc_root  [my clay get path]
        my variable reply_file
        set reply_file [file join $doc_root pkgIndex.tcl]
    }
}
clay::define ::test::content.time {
    method content {} {
        my variable reply_body
        set reply_body $::NOW
    }
}
clay::define ::test::content.error {
    method content {} {
        error {The programmer asked me to die this way}
    }
}
clay::define ::test::content.cgi {
    superclass ::httpd::content.cgi
}
clay::define ::test::content.string {
    method content {} {
        my variable reply_body
        set reply_body [my clay get hardcoded_string]
    }
}
clay::define ::httpd::test::reply {
    superclass ::httpd::reply ::test::content.echo
}

###
# Build the server
###

::httpd::server create TESTAPP port 10001 doc_root $::TESTDIR
TESTAPP plugin dict_dispatch
TESTAPP uri add * /      [list mixin {reply ::test::content.echo}]
TESTAPP uri add * /echo  [list mixin {reply ::test::content.echo}]
TESTAPP uri add * /file  [list mixin {reply ::test::content.file} path $::TESTDIR]
TESTAPP uri add * /time  [list mixin {reply ::test::content.time}]
TESTAPP uri add * /error [list mixin {reply ::test::content.error}]
TESTAPP uri add * /string  [list mixin {reply ::test::content.string} hardcoded_string apple]

# Catch all
#TESTAPP uri add * * [list mixin {reply httpd::content.echo}]

::DEBUG puts httpd-client-0001
test httpd-client-0001 {Do an echo request} -body {

    ::httpd::test::send 10001 {POST /echo HTTP/1.0} {} {THIS IS MY CODE}





} -match glob -result [200 {THIS IS MY CODE}]


::DEBUG puts httpd-client-0002
test httpd-client-0002 {Do another echo request} {
    ::httpd::test::send 10001 {POST /echo HTTP/1.0} {} {THOUGH THERE ARE MANY LIKE IT}





} [200 {THOUGH THERE ARE MANY LIKE IT} 29]


::DEBUG puts httpd-client-0003
test httpd-client-0003 {Do another echo request} -body {
    ::httpd::test::send 10001 {POST /echo HTTP/1.0} {} {THIS ONE ALONE IS MINE}





} -match glob -result [200 {THIS ONE ALONE IS MINE}]


::DEBUG puts httpd-client-0004
test httpd-client-0004 {URL Generates Error} -body {

    ::httpd::test::send 10001 {POST /error HTTP/1.0} {} {THIS ONE ALONE IS MINE}
} -match glob -result [500]















::DEBUG puts httpd-client-0005
test httpd-client-0005 {URL Different output with a different request} -body {
    ::httpd::test::send 10001 {POST /time HTTP/1.0} {} {THIS ONE ALONE IS MINE}


} -match glob -result [200 $::NOW]










::DEBUG puts httpd-client-0006
test httpd-client-0006 {Return a file} -body {
    ::httpd::test::send 10001 {GET /file HTTP/1.0} {} {}

} -result [IndexReply]

::DEBUG puts httpd-client-0007
test httpd-client-0007 {URL Generates Not Found} -body {

    ::httpd::test::send 10001 {POST /doesnotexist HTTP/1.0} {} {THIS ONE ALONE IS MINE}
} -match glob -result [404]




::DEBUG puts httpd-client-0008
test httpd-client-0008 {Pull a constant string} -body {

    ::httpd::test::send 10001 {GET /string HTTP/1.0} {} {}
} -match glob -result [200 apple]
# -------------------------------------------------------------------------
# Test proxies

clay::define ::test::content.proxy {
    superclass ::httpd::content.proxy

    method proxy_channel {} {
        return [::socket localhost [my clay get proxy_port]]
    }
}


::httpd::server create TESTPROXY port 10002 doc_root $::TESTDIR
TESTAPP   uri add * /proxy* [list mixin {reply ::test::content.proxy} proxy_port [TESTPROXY port_listening]]
TESTPROXY plugin dict_dispatch
TESTPROXY uri add * /       [list mixin {reply ::test::content.echo}]
TESTPROXY uri add * /echo   [list mixin {reply ::test::content.echo}]
TESTPROXY uri add * /file   [list mixin {reply ::test::content.file} path $::TESTDIR]
TESTPROXY uri add * /time   [list mixin {reply ::test::content.time}]
TESTPROXY uri add * /error  [list mixin {reply ::test::content.error}]
TESTPROXY uri add * /string   [list mixin {reply ::test::content.string} hardcoded_string banana]

## AKU ##
#
# Note: Proxy replies are not normalized to \n. They contain \r\n
# endings.  The old test::compare was ok with that due to running a
# trim on the lines it was comparing. Here we properly normalize
# before feeding into the comparison.
#
# Note 2: I suspect that this leakage / non-normalization of of \r\n
#         in the server is a bug which should be fixed. If so, norm-eol
#         becomes superfluous. Right now it feels like a band-aid

::DEBUG puts httpd-proxy-0001
test httpd-proxy-0001 {Do an echo request} -body {

    norm-eol [::httpd::test::send 10001 {POST /proxy/echo HTTP/1.0} {} {THIS IS MY CODE}]





} -match glob -result [200 {THIS IS MY CODE}]


::DEBUG puts httpd-proxy-0002
test httpd-proxy-0002 {Do another echo request} -body {
    norm-eol [::httpd::test::send 10001 {POST /proxy/echo HTTP/1.0} {} {THOUGH THERE ARE MANY LIKE IT}]





} -result [200 {THOUGH THERE ARE MANY LIKE IT} 29]


::DEBUG puts httpd-proxy-0003
test httpd-proxy-0003 {Do another echo request} -body {
    norm-eol [::httpd::test::send 10001 {POST /proxy/echo HTTP/1.0} {} {THIS ONE ALONE IS MINE}]





} -match glob -result [200 {THIS ONE ALONE IS MINE}]


::DEBUG puts httpd-proxy-0004
test httpd-proxy-0004 {URL Generates Error} -body {

    norm-eol [::httpd::test::send 10001 {POST /proxy/error HTTP/1.0} {} {THIS ONE ALONE IS MINE}]
} -match glob -result [500]















::DEBUG puts httpd-proxy-0005
test httpd-proxy-0005 {URL Different output with a different request} -body {
    norm-eol [::httpd::test::send 10001 {POST /proxy/time HTTP/1.0} {} {THIS ONE ALONE IS MINE}]


} -match glob -result [200 $::NOW]










::DEBUG puts httpd-proxy-0006
test httpd-proxy-0006 {Return a file} -body {
    norm-eol [::httpd::test::send 10001 {GET /proxy/file HTTP/1.0} {} {}]

} -result [IndexReply]

::DEBUG puts httpd-proxy-0008
test httpd-proxy-0008 {Pull a constant string} -body {
    norm-eol [::httpd::test::send 10001 {GET /proxy/string HTTP/1.0} {} {}]
} -result [200 banana 6]
# -------------------------------------------------------------------------
# cgi


TESTAPP plugin local_memchan
TESTAPP uri add * /cgi-bin* [list mixin {reply ::test::content.cgi} path $::TESTDIR/assets]



















::DEBUG puts httpd-cgi-0001
test httpd-cgi-0001 {CGI Post} -body {

    norm-eol [::httpd::test::send 10001 {POST /cgi-bin/test_cgi.tcl HTTP/1.0} {} {THIS IS MY CODE}]





} -match glob -result [200+status-conn {THIS IS MY CODE
}]

::DEBUG puts httpd-cgi-0002
test httpd-cgi-0002 {CGI Get} -body {

    ::httpd::test::send 10001 {GET /cgi-bin/test_cgi.tcl HTTP/1.0} {} {}




} -match glob -result [200+status-conn {Hi!
}]


###
# Test the local geturl method
###


test httpd-memchan-0001 {Memchan GET} {
    TESTAPP local_memchan geturl /time
} $NOW

# -------------------------------------------------------------------------
namespace eval ::scgi {}
namespace eval ::scgi::test {}

###
# Minimal test harness for the .tests
# Not intended for public consumption
# (But if you find it handy, please steal!)


proc ::scgi::encode_request {headers body info} {
    variable server_block

    dict set outdict CONTENT_LENGTH [string length $body]
    set outdict [dict merge $outdict $server_block $info]
    dict set outdict PWD [pwd]

    foreach {key value} $headers {
        if {$key in {
            DOCUMENT_ROOT
            HTTPS
            PATH
            REQUEST_METHOD REQUEST_URI
            REMOTE_HOST REMOTE_ADDR REMOTE_PORT
            SCRIPT_NAME
        } || [string range $key 0 5] eq "HTTP_"} {
            dict set outdict $key $value
        } else {
            dict set outdict HTTP_[string map {"-" "_"} [string toupper $key]] $value
        }
    }
    set result {}
    foreach {name value} $outdict {
        append result $name \x00 $value \x00
    }
    return "[string length $result]:$result,"
}

proc ::scgi::test::send {port headers body} {
    set sock [socket localhost $port]
    variable reply
    set reply($sock) {}

    if {![dict exists $headers HOST]} {
        dict set headers HOST localhost
    }
    dict set headers REMOTE_IP   127.0.0.1
    dict set headers REMOTE_HOST localhost

    chan configure $sock -translation binary -blocking 0 -buffering full -buffersize 4096
    chan event     $sock readable [list ::scgi::test::get_reply $sock]

    set block [::scgi::encode_request $headers $body {}]

    puts -nonewline $sock $block
    flush $sock
    puts -nonewline $sock $body
    flush $sock

    while {$reply($sock) eq {}} {
        update
    }

    #vwait [namespace current]::reply($sock)

    #puts ZZ\t[join [split $reply($sock) \n] "|\nZZ\t"]|
    return $reply($sock)
}

proc ::scgi::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)
    }
}

namespace eval ::scgi {
  variable server_block {SCGI 1.0 SERVER_SOFTWARE {TclScgiServer/0.1}}
}

###
# Build the reply class
###

::clay::define ::scgi::test::reply {
    superclass ::httpd::reply

    method reset {} {
        my variable reply_body
        my reply replace [my HttpHeaders_Default]
        set reply_body {}
    }
}

###
# Build the server
###

::clay::define scgi::test::app {
    superclass ::httpd::server.scgi

    clay set reply_class ::scgi::test::reply
}

::DEBUG puts [list ::test::content.file [info commands ::test::content.file]]

scgi::test::app create TESTSCGI port 10003 doc_root $::TESTDIR

TESTSCGI plugin dict_dispatch
TESTSCGI uri add * /      [list mixin {reply ::test::content.echo}]
TESTSCGI uri add * /echo  [list mixin {reply ::test::content.echo}]
TESTSCGI uri add * /file  [list mixin {reply ::test::content.file} path $::TESTDIR]
TESTSCGI uri add * /time  [list mixin {reply ::test::content.time}]
TESTSCGI uri add * /error [list mixin {reply ::test::content.error}]
TESTSCGI uri add * /string  [list mixin {reply ::test::content.string} hardcoded_string cherry]

::DEBUG puts scgi-client-0001
test scgi-client-0001 {Do an echo request} -body {

    ::scgi::test::send 10003 {REQUEST_METHOD POST REQUEST_URI /echo} {THIS IS MY CODE}





} -match glob -result [200+status-head {THIS IS MY CODE}]




::DEBUG puts scgi-client-0002
test scgi-client-0002 {Do another echo request} -body {
    ::scgi::test::send 10003 {REQUEST_METHOD POST REQUEST_URI /echo} {THOUGH THERE ARE MANY LIKE IT}





} -match glob -result [200+status-head {THOUGH THERE ARE MANY LIKE IT}]



::DEBUG puts scgi-client-0003
test scgi-client-0003 {Do another echo request} -body {
    ::scgi::test::send 10003 {REQUEST_METHOD POST REQUEST_URI /echo} {THIS ONE ALONE IS MINE}





} -match glob -result [200+status-head {THIS ONE ALONE IS MINE}]



::DEBUG puts scgi-client-0004
test scgi-client-0004 {URL Generates Error} -body {

    ::scgi::test::send 10003 {REQUEST_METHOD POST REQUEST_URI /error} {THIS ONE ALONE IS MINE}
} -match glob -result [500+status-head]

















::DEBUG puts scgi-client-0005
test scgi-client-0005 {URL Different output with a different request} -body {
    ::scgi::test::send 10003 {REQUEST_METHOD POST REQUEST_URI /time} {THIS ONE ALONE IS MINE}
} -match glob -result [200+status-head $::NOW]

::DEBUG puts scgi-client-0006
test scgi-client-0006 {Return a file} -body {
    ::scgi::test::send 10003 {REQUEST_METHOD GET REQUEST_URI /file} {}
} -result [IndexReply Status:]











::DEBUG puts scgi-client-0008
test scgi-client-0008 {Pull a constant string} -body {
    ::scgi::test::send 10003 {REQUEST_METHOD GET REQUEST_URI /string} {}
} -match glob -result [200+status-head cherry]


###

# Test the all object have been destroyed after ::clay::cleanup
###
test httpd-garbage-collection {Test that garbage collection leaves nothing behind} -body {
  ::clay::cleanup
  info commands ::httpd::object::*
} -result {}

::DEBUG puts all-tests-finished

# -------------------------------------------------------------------------

testsuiteCleanup

# Local variables:
# mode: tcl
# indent-tabs-mode: nil

Changes to modules/practcl/practcl.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
###
# Amalgamated package for practcl
# Do not edit directly, tweak the source in src/ and rerun
# build.tcl
###
package require Tcl 8.6
package provide practcl 0.16.3
namespace eval ::practcl {}

###
# START: httpwget/wget.tcl
###
package provide http::wget 0.1
package require http
::namespace eval ::http {
}
proc ::http::_followRedirects {url args} {
    while 1 {
        set token [geturl $url -validate 1]
        set ncode [ncode $token]
        if { $ncode eq "404" } {
          error "URL Not found"
        }
        switch -glob $ncode {
            30[1237] {### redirect - see below ###}
            default  {cleanup $token ; return $url}
        }
        upvar #0 $token state
        array set meta [set ${token}(meta)]
        cleanup $token
        if {![info exists meta(Location)]} {
           return $url
        }
        set url $meta(Location)
        unset meta
    }
    return $url
}
proc ::http::wget {url destfile {verbose 1}} {
    set tmpchan [open $destfile w]
    fconfigure $tmpchan -translation binary
    if { $verbose } {
        puts [list  GETTING [file tail $destfile] from $url]
    }
    set real_url [_followRedirects $url]
    set token [geturl $real_url -channel $tmpchan -binary yes]
    if {[ncode $token] != "200"} {
      error "DOWNLOAD FAILED"
    }
    cleanup $token
    close $tmpchan
}

###
# END: httpwget/wget.tcl
###
###
# START: clay/clay.tcl
###












<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1
2
3
4
5
6
7
8
9
10
11
12








































13
14
15
16
17
18
19
###
# Amalgamated package for practcl
# Do not edit directly, tweak the source in src/ and rerun
# build.tcl
###
package require Tcl 8.6
package provide practcl 0.16.3
namespace eval ::practcl {}

###
# START: httpwget/wget.tcl
###









































###
# END: httpwget/wget.tcl
###
###
# START: clay/clay.tcl
###
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
proc ::clay::define::destructor rawbody {
  set body {
# Run the destructor once and only once
set self [self]
my variable DestroyEvent
if {$DestroyEvent} return
set DestroyEvent 1
::clay::object_destroy $self
}
  append body $rawbody
  ::oo::define [current_class] destructor $body
}
proc ::clay::define::Dict {name {values {}}} {
  set class [current_class]
  set name [string trim $name :/]







<







966
967
968
969
970
971
972

973
974
975
976
977
978
979
proc ::clay::define::destructor rawbody {
  set body {
# Run the destructor once and only once
set self [self]
my variable DestroyEvent
if {$DestroyEvent} return
set DestroyEvent 1

}
  append body $rawbody
  ::oo::define [current_class] destructor $body
}
proc ::clay::define::Dict {name {values {}}} {
  set class [current_class]
  set name [string trim $name :/]
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
  }
}
proc ::clay::define::Variable {name {default {}}} {
  set class [current_class]
  set name [string trimright $name :/]
  $class clay set variable/ $name $default
}
proc ::clay::object_create {objname {class {}}} {
  #if {$::clay::trace>0} {
  #  puts [list $objname CREATE]
  #}
}
proc ::clay::object_rename {object newname} {
  if {$::clay::trace>0} {
    puts [list $object RENAME -> $newname]
  }
}
proc ::clay::object_destroy objname {
  if {$::clay::trace>0} {
    puts [list $objname DESTROY]
  }
  #::cron::object_destroy $objname
}
::namespace eval ::clay::define {
}
proc ::clay::ensemble_methodbody {ensemble einfo} {
  set default standard
  set preamble {}
  set eswitch {}
  if {[dict exists $einfo default]} {







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1028
1029
1030
1031
1032
1033
1034
















1035
1036
1037
1038
1039
1040
1041
  }
}
proc ::clay::define::Variable {name {default {}}} {
  set class [current_class]
  set name [string trimright $name :/]
  $class clay set variable/ $name $default
}
















::namespace eval ::clay::define {
}
proc ::clay::ensemble_methodbody {ensemble einfo} {
  set default standard
  set preamble {}
  set eswitch {}
  if {[dict exists $einfo default]} {
1729
1730
1731
1732
1733
1734
1735


















1736
1737
1738
1739
1740
1741
1742
        }
        foreach class $clayorder {
          if {[$class clay exists {*}$args]} {
            return $class
          }
        }
        return {}


















      }
      replace {
        set clay [lindex $args 0]
      }
      source {
        source [lindex $args 0]
      }







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
        }
        foreach class $clayorder {
          if {[$class clay exists {*}$args]} {
            return $class
          }
        }
        return {}
      }
      refcount {
        my variable refcount
        if {![info exists refcount]} {
          return 0
        }
        return $refcount
      }
      refcount_incr {
        my variable refcount
        incr refcount
      }
      refcount_decr {
        my variable refcount
        incr refcount -1
        if {$refcount <= 0} {
          ::clay::object_destroy [self]
        }
      }
      replace {
        set clay [lindex $args 0]
      }
      source {
        source [lindex $args 0]
      }
1858
1859
1860
1861
1862
1863
1864


1865
1866
1867
1868
1869
1870
1871
1872
1873

1874
1875
1876
1877
1878
1879
1880
1881
1882
1883























1884
1885
1886
1887
1888
1889
1890
  }
}
::clay::object clay branch array
::clay::object clay branch mixin
::clay::object clay branch option
::clay::object clay branch dict clay
::clay::object clay set variable DestroyEvent 0


::namespace eval ::clay::event {
}
proc ::clay::destroy args {
  if {![info exists ::clay::idle_destroy]} {
    set ::clay::idle_destroy {}
  }
  foreach object $args {
    if {$object in $::clay::idle_destroy} continue
    lappend ::clay::idle_destroy  $object

  }
}
proc ::clay::cleanup {} {
  if {![info exists ::clay::idle_destroy]} return
  foreach obj $::clay::idle_destroy {
    if {[info commands $obj] ne {}} {
      catch {$obj destroy}
    }
  }
  set ::clay::idle_destroy {}























}
proc ::clay::event::cancel {self {task *}} {
  variable timer_event
  variable timer_script

  foreach {id event} [array get timer_event $self:$task] {
    ::after cancel $event







>
>
|
<
|
<
<
|
<
<
<
>
|
<








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1819
1820
1821
1822
1823
1824
1825
1826
1827
1828

1829


1830



1831
1832

1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
  }
}
::clay::object clay branch array
::clay::object clay branch mixin
::clay::object clay branch option
::clay::object clay branch dict clay
::clay::object clay set variable DestroyEvent 0
if {[info commands ::cron::object_destroy] eq {}} {
  # Provide a noop if we aren't running with the cron scheduler
  namespace eval ::cron {}

  proc ::cron::object_destroy args {}


}



::namespace eval ::clay::event {
}

proc ::clay::cleanup {} {
  if {![info exists ::clay::idle_destroy]} return
  foreach obj $::clay::idle_destroy {
    if {[info commands $obj] ne {}} {
      catch {$obj destroy}
    }
  }
  set ::clay::idle_destroy {}
}
proc ::clay::object_create {objname {class {}}} {
  #if {$::clay::trace>0} {
  #  puts [list $objname CREATE]
  #}
}
proc ::clay::object_rename {object newname} {
  if {$::clay::trace>0} {
    puts [list $object RENAME -> $newname]
  }
}
proc ::clay::object_destroy args {
  if {![info exists ::clay::idle_destroy]} {
    set ::clay::idle_destroy {}
  }
  foreach objname $args {
    if {$::clay::trace>0} {
      puts [list $objname DESTROY]
    }
    ::cron::object_destroy $objname
    if {$objname in $::clay::idle_destroy} continue
    lappend ::clay::idle_destroy $objname
  }
}
proc ::clay::event::cancel {self {task *}} {
  variable timer_event
  variable timer_script

  foreach {id event} [array get timer_event $self:$task] {
    ::after cancel $event