# ------------------------------------------------------------ # Copyright (c) 2025-2025 # Q.W.Page Associates Inc. # www.qwpage.com # All rights reserved. # ------------------------------------------------------------ /* { This handler offers access from the wild, and retrieves information from the crm handler. */ } #::package require json; #::package require json::write; ::itcl::class ::qw::http_handler { /* { There is only one object of this class, i.e. ::qw::httpd::handler::singleton. It wraps the tclhttpd object of which there is one global object, unfortunately. However, we can have many handlers operating on the one and only port, i.e. generally 80 or 443, each with it's own url prefix. Each prefix is associated with one or more callbacks. A prefix/callback combination is registered by calling ::qw::httpd::handler::singleton .call_callback xxx Specific handlers register a callback with this handler. The callback is registered with a prefix and callback proc/method, usually called tcp_call_receive. This handler uses qw::http::singleton which is turn piggybacks on tclwebserver aka tclhttpd. We would like to replace tclhttpd with something simpler and something not in the glocal namespace. This object also displays some statistics in a window, as does tclhttpd so there are two windows up. */ } protected variable _call_callback_array; protected variable _post_callback_array; protected variable _title "qw_http_handler"; protected variable _stats_field_name_list [::list]; protected variable _toplevel ""; protected variable _stats; constructor {} { ::array set _call_callback_array {}; ::array set _post_callback_array {}; /* { We need the use an array for _stats because we use -textvariable in the window that displays them. We use this list to maintain the order in which they are displayed. */ } ::foreach {Name Text} { sargs_call_received "calls" sargs_call_return "returns" sargs_call_exception "errors" post_request "post requests" post_reply "post replies" post_error "post errors" ping_count "Pings" } { ::lappend _stats_field_name_list $Name; ::set _stats(.$Name.count) 0; ::set _stats(.$Name.text) $Text; } } method destructor {} { } method application_exit {} { ::set Result [::qw::dialog3::confirm .text "Ok to shut down qw_http_handler?"]; ::if {!$Result} { ::return; } ::itcl::delete object $this; ::qw::shutdown; } method main {sargs} { /* { 2.38.4 */ } ::set rwb1_debug 0 ::if {$rwb1_debug} {::puts "rwb1_debug,qw_httpd_handler,main,1000.0";} ::wm deiconify .; window_setup $sargs; ::if {$rwb1_debug} {::puts "rwb1_debug,qw_httpd_handler,main,1000.99";} } method stats_increment {StatName {Increment 1}} { ::if {[::qw::list::lsearch .list $_stats_field_name_list .pattern $StatName]<0} { ::qw::throw \ .text "[::namespace current]::[::qw::methodname] - invalid stats field name \"$StatName\"." \ .exception_id 20200819180200 \ ; } ::incr _stats(.$StatName.count) $Increment; # ::update idletasks; # ::update; } method window_setup {sargs} { ::set rwb1_debug 0; ::if {$rwb1_debug} {::puts "rwb1_debug,window_setup,1000.0";} ::set Count 0; ::while {[::winfo exists .handler_window$Count]} { ::incr Count 1; } ::set _toplevel .handler_window$Count; ::toplevel $_toplevel; # option add *font 9x15 ::wm title $_toplevel $_title; ::wm protocol $_toplevel WM_DELETE_WINDOW [::itcl::code $this application_exit]; ::wm protocol . WM_DELETE_WINDOW [::itcl::code $this application_exit]; # ::wm iconname $_toplevel [::file join $::qw_program_path nv2.ico]; # ::after idle [::list ::wm withdraw .]; # doesn't work in foreground ::wm iconname $_toplevel "$_title\n[::info hostname]"; ::append MessageText "$_title\n[::info hostname]"; # ::append MessageText "\nRoot:$_doc_root" ::append MessageText "\n[::clock format [::clock seconds] -format {%a %d-%b-%Y %H:%M:%S}]" ::message $_toplevel.message -text $MessageText -aspect 1000 -font "Arial 15"; ::grid $_toplevel.message -columnspan 2 -sticky news; /* { ::foreach {url label} { / "Home Page" } { label $_toplevel.l$url -text $label label $_toplevel.n$url -textvariable counterhit($url) -width 0 grid $_toplevel.l$url $_toplevel.n$url -sticky w grid configure $_toplevel.n$url -sticky e } */ } ::foreach Name $_stats_field_name_list { ::label $_toplevel.l$Name -text $_stats(.$Name.text) -font "Arial 12"; ::label $_toplevel.n$Name -textvariable [::itcl::scope _stats(.$Name.count)] -width 0 -font "Arial 12"; ::grid $_toplevel.l$Name $_toplevel.n$Name -sticky w; ::grid configure $_toplevel.n$Name -sticky e; } /* { Took off the quit button. The application shutdown, if any, should be controlled by the caller. */ } #::button $_toplevel.quit -text Quit -command [::itcl::code $this application_exit] -font "Arial 12"; #::grid $_toplevel.quit -columnspan 2; ::if {$rwb1_debug} {::puts "rwb1_debug,window_setup,1000.99";} } method ping_handler {Prefix Socket Suffix} { ::set rwb1_debug 0; ::if {$rwb1_debug} {::puts "rwb1_debug,ping_handler,1000.0,Prefix==$Prefix,Socket==$Socket,Suffix==$Suffix,clock==[::clock format [::clock seconds]]";} stats_increment "ping_count" 1; ::Httpd_ReturnData $Socket text/html "pong\n314120201202105335"; ::return; } method flat_handler_set {sargs} { /* { Client handlers register a callback here for calls from plugs. The callback is stored in the _call_callback array. */ } ::set rwb1_debug 0; ::if {$rwb1_debug} {::puts "rwb1_debug,flat_handler_set,1000.0,sargs==$sargs";} ::set Callback [::sargs::get $sargs .call_callback]; ::if {$Callback eq ""} { ::qw::bug 314120250206113157 "[::qw::methodname] - invalid callback \"$Callback\"."; } ::set Prefix [::sargs::get $sargs .prefix]; ::if {$Prefix eq ""} { ::qw::bug 314120250206113158 "[::qw::methodname] - invalid prefix \"$Prefix\"."; } ::if {[::info exists _call_callback_array($Prefix)]} { ::qw::bug 314120250206113159 "[::qw::methodname] - duplicate prefix \"$Prefix\"."; } ::set _call_callback_array($Prefix) $Callback; ::Url_PrefixInstall $Prefix [::list $this call_handler $Prefix]; ::if {$rwb1_debug} {::puts "rwb1_debug,flat_handler_set,1000.99,Prefix==$Prefix,handler==[::list $this call_handler]";} } method call_handler_set {sargs} { /* { Client handlers register a callback here for calls from plugs. The callback is stored in the _call_callback array. */ } ::set rwb1_debug 0; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler_set,1000.0,sargs==$sargs";} ::set Callback [::sargs::get $sargs .call_callback]; ::if {$Callback eq ""} { ::qw::bug 314120250110113157 "[::qw::methodname] - invalid callback \"$Callback\"."; } ::set Prefix [::sargs::get $sargs .prefix]; ::if {$Prefix eq ""} { ::qw::bug 314120250110113158 "[::qw::methodname] - invalid prefix \"$Prefix\"."; } ::if {[::info exists _call_callback_array($Prefix)]} { ::qw::bug 314120250110113159 "[::qw::methodname] - duplicate prefix \"$Prefix\"."; } ::set _call_callback_array($Prefix) $Callback; ::Url_PrefixInstall $Prefix [::list $this call_handler $Prefix]; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler_set,1000.99,Prefix==$Prefix,handler==[::list $this call_handler]";} } method call_handler_unset {sargs} { /* { */ } ::set Prefix [::sargs::get $sargs .prefix]; ::if {$Prefix eq ""} { ::qw::bug 314120250114110614 "[::qw::methodname] - invalid prefix \"$Prefix\"."; } ::if {[::info exists _call_callback_array($Prefix)]} { ::unset _call_callback_array($Prefix); } } method call_handler {Prefix Socket Suffix} { /* { All prefixes redirect calls here. We make a standard call and redirect the call_callback. */ } /* { When the handler receives a call from a plug that has the specified prefix, the args are processed into a sargs and used to call the callback. We watch for results versus exceptions and pass the corresponding result to the plug. */ } ::set rwb1_debug 0; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.0,Prefix==$Prefix,Socket==$Socket,Suffix==$Suffix,_call_callback==$_call_callback_array($Prefix)";} ::upvar #0 Httpd$Socket Data; # ::qw::profile::begin "http_call_handler"; # ::qw::finally [::list ::qw::profile::end "http_call_handler"]; # ::qw::finally [::list ::qw::profile::end "http_call_handler"]; ::qw::profile::finally "http_call_handler"; ::if {1} { ::if {$Data(proto) eq "OPTIONS"} { ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.1,Data(proto)==$Data(proto)";} ::set Text { Access-Control-Allow-Origin: http:80//benn7/message_database Access-Control-Allow-Methods: POST Access-Control-Allow-Headers: Content-Type Access-Control-Max-Age: 86400 } ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.2,text==\n$Text";} ::Httpd_ReturnData $Socket text/html $Text; ::return; } } ::set ContentType ""; # ------------------------------------------------------------ # Get Content-Type from Data(headerlist) # ------------------------------------------------------------ ::if {[::info exists Data(headerlist)]} { ::foreach {Name Value} $Data(headerlist) { ::if {$Name eq "Content-Type"} { ::set ContentType $Value; ::break; } } } # ------------------------------------------------------------ # Process a request. # ------------------------------------------------------------ ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.4,Prefix==$Prefix,Socket==$Socket,Suffix==$Suffix";} ::if {$rwb1_debug} { ::qw::httpd::singleton debug_dump $Socket [::qw::methodname]; } # ------------------------------------------------------------ # Find handler for the url. # ------------------------------------------------------------ ::switch -- $ContentType { "application/sargs" { ::set Query $Data(query); } "application/json" { ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.5,Data(query)==$Data(query)";} ::set Query [::sargs::json2sargs .json $Data(query)]; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.6,Query==$Query";} } "text/javascript" { ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.7,Data(query)==$Data(query)";} ::set Query [::sargs::json2sargs .json $Data(query)]; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.8,Query==$Query";} } "text/html" { ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.9";} ::Httpd_ReturnData $Socket text/html "Ok"; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.10";} ::return; ::qw::throw \ .text "[::qw::methodname] - Invalid content-type \"$ContentType\"." \ .error_id 314120210121103743 \ ; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.11,Data(query)==$Data(query)";} ::set Query [::sargs::json2sargs .json $Data(query)]; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.12,Query==$Query";} } default { ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.13";} ::Httpd_ReturnData $Socket text/html "Ok"; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.14";} ::return; ::qw::throw \ .text "[::qw::methodname] - Invalid content-type \"$ContentType\"." \ .error_id 314120210121103742 \ ; } } ::if {$Query ne ""&&[::sargs::is_primitive $Query]} { ::qw::throw \ .text "[::namespace current]::[::qw::methodname] - invalid query $Query." \ .error_id 314120200502084609 \ ; } ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.15,Query==\n[::sargs::format $Query]";} /* { Typical query: .command {registration_record_get .serial INTERNAL-BENN-1} .tcp { .command call .priority foreground } */ } ::set TcpCommand [::sargs::get $Query .tcp.command] ::switch -- $TcpCommand { "call" { /* { This is a call (remote_call) from a ::qw::http::plug. The .tcp componenet will contain .command call and .priority foreground. */ } ::set TcpPriority [::sargs::get $Query .tcp.priority]; stats_increment "sargs_call_received" 1; ::if {$TcpPriority ne "foreground"} { ::Httpd_Error $Socket 404; ::return; } ::set Exception ""; ::qw::try { # ------------------------------------------------------------ # Make the call to the application callback, i.e. tcp_call_receive Command sargs. # ------------------------------------------------------------ ::if {![::info exists _call_callback_array($Prefix)]} { ::qw::bug 314120250110114507 "[::qw::methodname] - can't find handler for prefix \"$Prefix\"."; } /* { We increment the return stats here so we are all set to return anywhere in the code below without the need to increment the stats. But if we encounter an exception we decrement the return stat. */ } stats_increment "sargs_call_return" 1; ::set QueryCommand [::sargs::get $Query .command]; ::set FirstWord [::lindex $QueryCommand 0]; ::if {[::qw::command_exists $FirstWord] \ ||[::string first "::qw::odb::" $FirstWord]==0 \ ||[::info exists ::auto_index($FirstWord)]} { # ------------------------------------------------------------ # We hit the "evaluate" callback sub_command. # ------------------------------------------------------------ /* { The command exists and we could evaluate it right here but that would be a security breach. Instead, we hit the "evaluate" sub_command in the callback, thus allowing the application to manage security. This is not just passing the buck. It is not our place to manage security and each application can manage security differently. But what are the three conditions in the complex OR above under which we ask the application callback to evaluate the command script? (1) QueryCommand exists. (2) The command does not exist but it is the command for an odb object. When evaluated, the object manager will automatically load the object. (3) The command does not exist but will be loaded by the tclIndex/auto_path mechanism. The conditionals are tested in a specific order. We should enhance the test above to match an odb object address more closely using a regular expression. Below are sample odb addresses, one being the root object which was "special" and thus different from other addresses. ::qw::odb::20210928094849::/1632836929_2 ::qw::odb::20210928094849::/1632836929_2.postings.index/date ::qw::odb::20210928094849::/1 */ } ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.16,QueryCommand==$QueryCommand";} ::set Callback $_call_callback_array($Prefix); ::set Result [::eval $Callback evaluate $Query]; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.17,Result==$Result";} ::Httpd_ReturnData $Socket $ContentType [::sargs .result $Result]; ::return $Result; } /* { The callback can be a proc or a method so it generally consists of one or two words followed by path value pairs. */ } ::set Callback $_call_callback_array($Prefix); ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.18,callback==$Callback";} ::set TclCommand [::lindex $Callback 0]; ::puts "rwb,call_handler,1000.18.0"; ::if {![::qw::command_exists $TclCommand]} { /* { The first word of the callback must be an existing command. */ } ::puts "rwb,call_handler,1000.18.1"; ::qw::bug 314120250116103306 "[::qw::methodname] - no command \"$TclCommand\"."; } ::set iTclMethod [::lindex $Callback 1]; ::puts "rwb,call_handler,1000.18.2"; ::if {[::string first "tcp_" $iTclMethod]==0} { /* { This sould be something like "method tcp_call_receive {Command sargs}". so we extract the command as a separate argument. Otherwise we assume its just "method some_method {sargs}". */ } ::puts "rwb,call_handler,1000.18.3"; ::set TcpCommand [::lindex [::sargs::get $Query .command] 0]; ::set sargs [::sargs]; ::foreach {Path Value} [::lrange [::sargs::get $Query .command] 1 end] { ::sargs::var::set sargs $Path $Value; } ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,3000.1000.19,TclCommand==$TclCommand,iTclMethod==$iTclMethod,sargs==$sargs";} ::set Result [$TclCommand $iTclMethod $TcpCommand $sargs]; ::if {$rwb1_debug} { ::set TempResult $Result; ::if {[::sargs::exists $TempResult .base64]} { /* { When testing very big files which were converted the base64, we have extremely long .lst files. */ } ::sargs::var::unset TempResult .base64; ::puts "rwb1_debug,call_handler,3000.1000.20,base64_size==[::string length [::sargs::get $Result .base64]],Result==$TempResult"; } } ::Httpd_ReturnData $Socket $ContentType [::sargs .result $Result]; ::return $Result; } /* { The callback is not a tcp_method. */ } ::puts "rwb,call_handler,1000.18.4"; ::set TcpCommand [::lindex [::sargs::get $Query .command] 0]; ::puts "rwb,call_handler,1000.18.5"; ::set sargs [::sargs]; ::puts "rwb,call_handler,1000.18.6"; ::foreach {Path Value} [::sargs::get $Query .command] { ::puts "rwb,call_handler,1000.18.7"; ::sargs::var::set sargs $Path $Value; ::puts "rwb,call_handler,1000.18.8"; } ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.21,Callback==$Callback,sargs==$sargs";} ::puts "rwb,call_handler,1000.18.9"; ::set Result [::eval [::concat $Callback $sargs]] ::puts "rwb,call_handler,1000.18.10"; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.22,Result==$Result";} ::puts "rwb,call_handler,1000.18.11"; ::return $Result; } catch Exception { # ------------------------------------------------------------ # Exception was thrown by application. # ------------------------------------------------------------ ::qw::try { ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.23,Exception==$Exception";} stats_increment "sargs_call_exception" 1; stats_increment "sargs_call_return" -1; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.24";} ::switch -- $ContentType { "application/sargs" { ::set Response [::sargs .exception $Exception]; } "application/json" { ::if {![::sargs::is_primitive $Exception]} { ::set Exception [::sargs::sargs2json .sargs $Exception]; } ::set Response [::subst { {"exception":"$Exception"} }]; } default { # rwb_todo return some kind of error } } ::Httpd_ReturnData $Socket $ContentType $Response; # ::Httpd_ReturnData $Socket text/html [::sargs .exception $Exception]; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.25";} } catch dummy { ::qw::warning 314120201006115111 "[::namespace current]::[::qw::methodname] - can't write socket, exception:$dummy"; } ::return; } # ------------------------------------------------------------ # Application succeeded and returned a result. # ------------------------------------------------------------ ::qw::try { stats_increment "sargs_call_return" 1; ::switch -- $ContentType { "application/sargs" { ::set Response [::sargs .result $Result]; } "application/json" { ::set Result [::sargs::insert_backslashes_for_json .structure $Result]; ::set Response [::subst { {"result":"$Result"} }]; } default { # rwb_todo return some kind of error } } ::Httpd_ReturnData $Socket $ContentType $Response; } catch Exception { ::qw::warning 314120200405152219 "[::namespace current]::[::qw::methodname] - can't write socket, exception:$Exception"; ::return; } ::return; } } ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.26";} ::Httpd_ReturnData $Socket text/html [::sargs .exception "Invalid command $TcpCommand"]; stats_increment "sargs_call_exception" 1; ::if {$rwb1_debug} {::puts "rwb1_debug,call_handler,1000.99";} } method post_handler_set {sargs} { /* { Client handlers register a callback here for calls from plugs. The callback is stored in the _call_callback array, indexed by the prefix. */ } ::set rwb1_debug 0; ::if {$rwb1_debug} {::puts "rwb1_debug,post_handler_set,1000.0,sargs==$sargs";} ::set Callback [::sargs::get $sargs .post_callback]; ::if {$Callback eq ""} { ::qw::bug 314120250120151651 "[::qw::methodname] - invalid callback \"$Callback\"."; } ::set Prefix [::sargs::get $sargs .prefix]; ::if {$Prefix eq ""} { ::qw::bug 314120250120151652 "[::qw::methodname] - invalid prefix \"$Prefix\"."; } ::if {[::info exists _post_callback_array($Prefix)]} { ::qw::bug 314120250120151653 "[::qw::methodname] - duplicate prefix \"$Prefix\"."; } ::set _post_callback_array($Prefix) $Callback; ::Url_PrefixInstall $Prefix [::list $this post_handler $Prefix]; ::if {$rwb1_debug} {::puts "rwb1_debug,post_handler_set,1000.99,Prefix==$Prefix,handler==[::list $this post_handler]";} } method post_handler_unset {sargs} { /* { */ } ::set Prefix [::sargs::get $sargs .prefix]; ::if {$Prefix eq ""} { ::qw::bug 314120250114110614 "[::qw::methodname] - invalid prefix \"$Prefix\"."; } ::if {[::info exists _post_callback_array($Prefix)]} { ::unset _post_callback_array($Prefix); } } method post_handler {Prefix Socket Suffix} { /* { We have processed the input and set up the state array. Now is the time to interpret the request and call the right handler. Incoming queries are directed here where we can process the query args for sargs=xxx and jargs=yyy. These handlers take sargs (or jargs) and return a value which is sent to the caller as .result or they catch an exception which is send to the caller as .exception. However you can also use Url_PrefixInstall to install handlers directly. In that case you must call Httpd_ReturnData or Httpd_Error. The return value from the handler is not used. You are on your own. See /ping for an example. */ } ::set rwb1_debug 0; stats_increment "post_request" 1; ::if {$rwb1_debug} {::puts "rwb1_debug,post_handler,1000.0,Prefix==$Prefix,Socket==$Socket,Suffix==$Suffix";} ::upvar #0 Httpd$Socket Data; # ------------------------------------------------------------ # Process a request. # ------------------------------------------------------------ ::qw::profile::finally "profile_asynch_post_to_mothership"; /* { _data(url) contains the trailing path such as /test_handler. */ } # ------------------------------------------------------------ # Find handler for the url. # ------------------------------------------------------------ ::set Url $Prefix; ::if {![::info exists Data(query)]} { ::if {$rwb1_debug} {::puts "rwb1_debug,post_handler,1000.2";} stats_increment "post_error" 1; ::Httpd_Error $Socket 404; ::return ""; } ::if {$Data(query) eq ""} { ::if {$rwb1_debug} {::puts "rwb1_debug,post_handler,1000.3";} stats_increment "post_error" 1; ::Httpd_Error $Socket 404; ::return ""; } # 2.34.5 - found out how to make Url_DecodeQuery properly. ::set Query [::Url_DecodeQuery $Data(query)]; ::if {$rwb1_debug} {::puts "rwb1_debug,post_handler,1000.4";} ::set QueryName [::lindex $Query 0]; ::if {$rwb1_debug} {::puts "rwb1_debug,post_handler,1000.5,QueryName==$QueryName";} ::set QueryArguments [::lindex $Query 1]; ::if {$rwb1_debug} {::puts "rwb1_debug,post_handler,1000.6,QueryArguments==\n[::sargs::format $QueryArguments]";} ::set sargs $QueryArguments; ::if {$QueryArguments ne ""&&[::sargs::is_primitive $QueryArguments]} { ::qw::bug 314120250120154511 "[::namespace current]::[::qw::methodname] - invalid query_arguments $QueryArguments."; } ::if {![::info exists _post_callback_array($Prefix)]} { ::qw::bug 314120250127153605 "[::namespace current]::[::qw::methodname] - no callback for prefix \"$Prefix\"."; } ::if {![::qw::command_exists [::lindex $_post_callback_array($Prefix) 0]]} { ::qw::bug 314120250127153606 "[::namespace current]::[::qw::methodname] - invalid callback \"$_post_callback_array($Prefix)\" for prefix \"$Prefix\"."; } ::set Callback $_post_callback_array($Prefix); ::qw::try { ::if {$rwb1_debug} {::puts "rwb1_debug,post_handler,1000.10,Callback==$Callback,sargs==\n[::sargs::format $sargs]";} ::set Result [::eval $Callback $QueryArguments]; ::if {$rwb1_debug} {::puts "rwb1_debug,post_handler,1000.11,result==\n$Result";} } catch Exception { ::if {$rwb1_debug} {::puts "rwb1_debug,post_handler,1000.12,exception==$Exception";} } ::Httpd_ReturnData $Socket text/html $Result; ::if {$rwb1_debug} {::puts "rwb1_debug,post_handler,1000.99";} stats_increment "post_reply" 1; } } ::qw::http_handler ::qw::http_handler::singleton; ::qw::http_handler::singleton main;