Index: openacs-4/packages/xml-rpc/xml-rpc.info =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/xml-rpc.info,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/xml-rpc.info 26 Nov 2003 02:59:13 -0000 1.1 @@ -0,0 +1,28 @@ + + + + + XML-RPC Server + XML-RPC Server + f + t + RPC2 + + + Vinod Kurup + A simple XML-RPC server. + 2003-10-09 + Vinod Kurup + This package implements a simple XML-RPC server. It receives XML-RPC calls, decodes the XML and then calls the requested method on the OpenACS server. Code is based on ns_xmlrpc, but now uses tDOM. + + + + + + + + + + + + Index: openacs-4/packages/xml-rpc/tcl/system-init.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/tcl/system-init.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/tcl/system-init.tcl 26 Nov 2003 02:59:13 -0000 1.1 @@ -0,0 +1,12 @@ +# /packages/xml-rpc/tcl/system-init.tcl +ad_library { + Register standard system procs + @author Vinod Kurup [vinod@kurup.com] + @creation-date Thu Oct 9 22:21:14 2003 + @cvs-id $Id: system-init.tcl,v 1.1 2003/11/26 02:59:13 vinodk Exp $ +} + +xmlrpc::register_proc system.listMethods +xmlrpc::register_proc system.methodHelp +xmlrpc::register_proc system.multicall +xmlrpc::register_proc system.add Index: openacs-4/packages/xml-rpc/tcl/system-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/tcl/system-procs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/tcl/system-procs.tcl 26 Nov 2003 02:59:13 -0000 1.1 @@ -0,0 +1,114 @@ +# /packages/xml-rpc/tcl/system-procs.tcl +ad_library { + Standard reserved nmethods + http://xmlrpc.usefulinc.com/doc/reserved.html + + @author Vinod Kurup [vinod@kurup.com] + @creation-date Thu Oct 9 22:14:04 2003 + @cvs-id $Id: system-procs.tcl,v 1.1 2003/11/26 02:59:13 vinodk Exp $ +} + +ad_proc -public system.listMethods {} { + Enumerate the methods implemented by the XML-RPC server. + + The system.listMethods method requires no parameters. + + @return an array of strings, each of which is the name of a method + implemented by the server. + @author Vinod Kurup +} { + set result [list] + foreach proc_name [xmlrpc::list_methods] { + lappend result [list -string $proc_name] + } + + return [list -array $result] +} + +# system.methodSignature not implemented because we don't keep track of +# parameter types or return types + +ad_proc -public system.methodHelp { + methodName +} { + This method takes one parameter, the name of a method implemented by + the XML-RPC server. + + @param methodName method implemented in XML-RPC + @return a documentation string describing the use of that method. + If no such string is available, an empty string is returned. The + documentation string may contain HTML markup. + @author Vinod Kurup +} { + return [list -string [api_proc_documentation $methodName]] +} + +ad_proc -public system.multicall { + array +} { +

+ Perform multiple requests in one call - see + http://www.xmlrpc.com/discuss/msgReader$1208 +

+ +

+ Takes an array of XML-RPC calls encoded as structs of the form (in a + Pythonish notation here): +

+    {'methodName': string, 'params': array}
+    
+

+ @param array array of structs containing XML-RPC calls + @return an array of responses. There will be one response for each call + in the original array. The result will either be a one-item array + containing the result value - this mirrors the use of <params> in + <methodResponse> - or a struct of the form found inside the + standard <fault> element. + @author Vinod Kurup +} { + set responses [list] + + foreach call $array { + # parse the call for methodName and params + if { [catch { + array unset c + array set c $call + set method $c(methodName) + set params $c(params) + } errmsg ] } { + # if we can't get a methodName and params, then fault + lappend responses [list -struct \ + [list faultCode [list -int 5] \ + faultString "Invalid request. $errmsg" + ]] + } else { + # call the method + set errno [catch {xmlrpc::invoke_method $method $params} result] + if { $errno } { + # fault + lappend responses [list -struct \ + [list faultCode [list -int $errno] \ + faultString $result]] + } else { + lappend responses $result + } + } + } + return [list -array $responses] +} + +ad_proc -public system.add { + args +} { + Simple test function. + Add a variable number of integers. + + @param args variable number of integers + @return integer sum +} { + set sum 0 + foreach value $args { + incr sum $value + } + return [list -int $sum] +} Index: openacs-4/packages/xml-rpc/tcl/validator-init.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/tcl/validator-init.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/tcl/validator-init.tcl 26 Nov 2003 02:59:13 -0000 1.1 @@ -0,0 +1,17 @@ +# /packages/xml-rpc/tcl/validator-init.tcl +ad_library { + Register validator XML-RPC procs + @author Vinod Kurup [vinod@kurup.com] + @creation-date Fri Oct 3 19:25:19 2003 + @cvs-id $Id: validator-init.tcl,v 1.1 2003/11/26 02:59:13 vinodk Exp $ +} + +xmlrpc::register_proc validator1.arrayOfStructsTest +xmlrpc::register_proc validator1.countTheEntities +xmlrpc::register_proc validator1.easyStructTest +xmlrpc::register_proc validator1.echoStructTest +xmlrpc::register_proc validator1.manyTypesTest +xmlrpc::register_proc validator1.moderateSizeArrayCheck +xmlrpc::register_proc validator1.nestedStructTest +xmlrpc::register_proc validator1.simpleStructReturnTest + Index: openacs-4/packages/xml-rpc/tcl/validator-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/tcl/validator-procs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/tcl/validator-procs.tcl 26 Nov 2003 02:59:13 -0000 1.1 @@ -0,0 +1,307 @@ +# /packages/xml-rpc/tcl/validator-procs.tcl +ad_library { + XML-RPC Validation Test + Written by, Aaron Swartz + Enhanced by Jerry Asher + Edited for xml-rpc package by Vinod Kurup + contains: + xml-rpc server validation implementation + xml-rpc client validator test implementation + + @creation-date Thu Oct 9 22:14:04 2003 + @cvs-id $Id: validator-procs.tcl,v 1.1 2003/11/26 02:59:13 vinodk Exp $ +} + +############################################################ +# The xml-rpc client validator procedures begin here: + +# Takes an array, each of whose members is a struct. Return the +# sum of all the values named curly from each struct. + + +proc validator1.arrayOfStructsTest {params} { + set number 0 + foreach {param} $params { + array set struct $param + incr number $struct(curly) + } + return [list -int $number] +} + + +# Takes a string. +# Return the number of each entity in a struct. + +proc validator1.countTheEntities {args} { + set string $args + + # For each type of entity, do a regsub -all and return the result as an + # integer, then place it all in a struct with the proper names and return + # it. + + return \ + [list -struct \ + [list ctLeftAngleBrackets \ + [list -int [regsub -all {\<} $string "" string]] \ + ctRightAngleBrackets \ + [list -int [regsub -all {\>} $string "" string]] \ + ctAmpersands \ + [list -int [regsub -all {&} $string "" string]] \ + ctApostrophes \ + [list -int [regsub -all {\'} $string "" string]] \ + ctQuotes \ + [list -int [regsub -all {\"} $string "" string]] + ] + ] +} + + +# Takes a struct. +# Return the sum of the values larry, curly and moe. + +proc validator1.easyStructTest {struct} { + # De-list-ify the stuct: + array set bigStruct $struct + # Return the sum as an integer: + return [list -int [expr \ + $bigStruct(moe) \ + + $bigStruct(curly) \ + + $bigStruct(larry)]] +} + + +proc validator1.echoStructTest {struct} { + foreach {name value} $struct { + if {[llength $value] > 1} { + # For the substructs: + foreach {name2 value2} $value { + set returnArray($name2) [list -int $value2] + } + set output($name) [list -struct [array get returnArray]] + } else { + set output($name) $value + } + array set returnArray "" + } + return [list -struct [array get output]] +} + + +proc validator1.manyTypesTest { + number boolean string double dateTime base64 +} { + return [list -array \ + [list \ + [list -int $number] \ + [list -boolean $boolean] \ + [list -text $string] \ + [list -double $double] \ + [list -date [clock format $dateTime]] \ + [list -base64 $base64]]] +} + + +proc validator1.moderateSizeArrayCheck {array} { +# array set bigArray $array +# set counter 0 +# while {[info exists bigArray($counter)]} { +# incr counter +# } +# set counter [expr $counter - 1] +# return "-string [list "$bigArray(0)$bigArray($counter)"]" + return "-string \"[lindex $array 0][lindex $array end]\"" +} + + +proc validator1.nestedStructTest {struct} { + array set bigStruct $struct + array set 2000 $bigStruct(2000) + array set April $2000(04) + array set first $April(01) + return "-int [expr $first(larry) + $first(curly) + $first(moe)]" +} + + +proc validator1.simpleStructReturnTest {number} { + set struct(times10) [list -int [expr $number * 10]] + set struct(times100) [list -int [expr $number * 100]] + set struct(times1000) [list -int [expr $number * 1000]] + + return "-struct [list [array get struct]]" +} + + +############################################################ +# XML-RPC Server Validator +# change URL to the server you are trying to validate! for each of +# these procs.. + +proc validate1.arrayOfStructsTest { +{url http://www.theashergroup.com/RPC2} + {array ""} +} { + if {[string equal "" $array]} { + set array [list \ + [list -struct [list moe [list -int 1] \ + curly [list -int 2] \ + larry [list -int 3]]] \ + [list -struct [list moe [list -int 1] \ + curly [list -int 2] \ + larry [list -int 3]]] \ + [list -struct [list moe [list -int 1] \ + curly [list -int 2] \ + larry [list -int 3]]]] + } + return [xmlrpc::remote_call $url validator1.arrayOfStructsTest -array $array] +} + +proc validate1.countTheEntities { + {url http://www.theashergroup.com/RPC2} + {string ""} + } { + if {[string equal "" $string]} { + set string "l'&d>&f&x'>jsua\"&'wmq&'nk'i'" + } + + return [xmlrpc::remote_call $url validator1.countTheEntities -string $string] +} + +proc validate1.easyStructTest { + {url "http://www.theashergroup.com/RPC2"} + {struct ""} +} { + if {[string equal "" $struct]} { + set struct \ + [list moe [list -int 1] \ + curly [list -int 2] \ + larry [list -int 3]] + + } + return [xmlrpc::remote_call $url validator1.easyStructTest -struct $struct] +} + +proc validate1.echoStructTest { + {url "http://www.theashergroup.com/RPC2"} + {struct ""} +} { + if {[string equal $struct ""]} { + set struct [list bob [list -int 5]] + } + return [xmlrpc::remote_call $url validator1.echoStructTest -struct $struct] +} + +proc validate1.manyTypesTest { + {url http://www.theashergroup.com/RPC2} + {int 1} + {boolean 0} + {string wazzup} + {double 3.14159} + {date "20010704T11:50:30"} + {base64 "R0lGODlhFgASAJEAAP/////OnM7O/wAAACH5BAEAAAAALAAAAAAWABIAAAJAhI+py40zDIzujEDBzW0n74AaFGChqZUYylyYq7ILXJJ1BU95l6r23RrRYhyL5jiJAT/Ink8WTPoqHx31im0UAAA7"} +} { + return [xmlrpc::remote_call $url validator1.manyTypesTest \ + -int $int -boolean $boolean -string $string \ + -double $double -date $date -base64 $base64] +} + +proc validate1.moderateSizeArrayCheck { + {url http://www.theashergroup.com/RPC2} + {array ""} + } { + if {[string equal "" $array]} { + set array [list Wisconsin Vermont Utah Idaho Kansas California \ + Virginia Iowa {New York} Mississippi Maine Delaware \ + Ohio Washington {West Virginia} Delaware Kentucky \ + {Rhode Island} Hawaii Oregon Kansas {South Carolina} \ + Maine Louisiana {West Virginia} Nebraska Georgia \ + {North Dakota} {North Dakota} Hawaii California Hawaii \ + {South Dakota} Texas Kentucky Alaska Pennsylvania \ + Missouri Ohio Wisconsin Hawaii Pennsylvania \ + Utah Alabama Ohio Michigan Idaho \ + Montana {New York} Arizona Alaska Vermont \ + {North Carolina} Washington Alabama {New Mexico} Utah \ + Nevada {South Dakota} Oklahoma Arizona Mississippi \ + {New York} Illinois {North Carolina} Georgia Wisconsin \ + Pennsylvania Wisconsin Minnesota Arkansas Alaska \ + Iowa Louisiana {West Virginia} Georgia Arizona \ + Washington Wisconsin Delaware {South Dakota} Delaware \ + Kentucky {North Dakota} Wisconsin Connecticut Alabama \ + Delaware Colorado Alabama {New Mexico} Iowa \ + Michigan Wyoming Oklahoma {South Dakota} Kentucky \ + Massachusetts Hawaii {North Carolina} Virginia \ + Delaware Wyoming Colorado Louisiana {West Virginia} \ + Michigan Utah Connecticut Oklahoma {South Dakota} \ + {South Dakota} California Minnesota {Rhode Island} \ + Georgia Kansas Kentucky Michigan Wyoming Nevada \ + Missouri {New York} Maine Oregon Tennessee {New York} \ + Washington Connecticut {South Dakota} Wyoming \ + Minnesota {South Dakota} {New York} {West Virginia} \ + Hawaii {North Dakota} Ohio Washington Delaware \ + Massachusetts Nebraska Texas {New York}] + } + return [xmlrpc::remote_call $url validator1.moderateSizeArrayCheck -array $array] +} + +proc validate1.nestedStructTest { + {url http://www.theashergroup.com/RPC2} + {moe 1} + {larry 2} + {curly 4} + {startyear 1999} + {endyear 2001} +} { + + set calendar "" + # for each year + for {set y $startyear} {$y <= $endyear} {incr y} { + + set year [list] + # for each month + for {set m 1} {$m <= 12} {incr m} { + + set month [list] + # for each day + set mstr [format %02d $m] + for {set d 1} {$d <= 31} {incr d} { + set dstr [format %02d $d] + # exit test (to find end of month) + set date \ + [clock format \ + [clock scan "[expr $d - 1] day" \ + -base [clock scan "$y-${mstr}-01"]] \ + -format "%y:%m:%d"] + set date [split $date :] + set reald [lindex $date 2] + if {![string equal $reald $dstr]} { + break + } + + if {($y == 2000) && ($m == 4) && ($d == 1)} { + set dayta \ + [list -struct \ + [list moe [list -int $moe] \ + curly [list -int $curly] \ + larry [list -int $larry]]] + } else { + set dayta \ + [list -struct \ + [list moe [list -int [expr 2 * $moe]]]] + } + set month [concat $month [list $dstr $dayta]] + } + set year [concat $year [list $mstr [list -struct $month]]] + } + set calendar [concat $calendar [list $y [list -struct $year]]] + } + + return [xmlrpc::remote_call $url validator1.nestedStructTest -struct $calendar] +} + + +proc validate1.simpleStructReturnTest { + {url http://www.theashergroup.com/RPC2} + {number 2} +} { + return [xmlrpc::remote_call $url validator1.simpleStructReturnTest -int $number] +} + Index: openacs-4/packages/xml-rpc/tcl/xml-rpc-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/tcl/xml-rpc-procs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/tcl/xml-rpc-procs.tcl 26 Nov 2003 02:59:13 -0000 1.1 @@ -0,0 +1,708 @@ +# /packages/xml-rpc/tcl/xml-rpc-procs.tcl +ad_library { +

+ Initially created by Dave Bauer 2001-03-30 with inspiration from + Steve Ball and help from Aaron Swartz and Jerry Asher. +

+

+ Modified by Vinod Kurup to +

    +
  1. Use the xml abstraction procs in + packages/acs-tcl/tcl/30-xml-utils-procs.tcl (which use tDom now)
  2. +
  3. Fit in OpenACS 5 framework
  4. +
+

+ + @author Vinod Kurup [vinod@kurup.com] + @creation-date 2003-09-30 + @cvs-id $Id: xml-rpc-procs.tcl,v 1.1 2003/11/26 02:59:13 vinodk Exp $ +} + +# setup nsv array to hold procs that are registered for xml-rpc access +nsv_array set xmlrpc_procs [list] + +namespace eval xmlrpc {} + +ad_proc -public xmlrpc::url {} { + @return the URL that is listening for RPC requests + + @author Vinod Kurup +} { + # ok to use this since this is a singleton package. + return [apm_package_url_from_key xml-rpc] +} + +ad_proc -public xmlrpc::enabled_p {} { + @return whether the server is enabled +} { + return [parameter::get_from_package_key \ + -package_key xml-rpc \ + -parameter EnableXMLRPCServer] +} + +ad_proc -public xmlrpc::list_methods {} { + @return alphabetical list of XML-RPC procs on this server +} { + return [lsort [nsv_array names xmlrpc_procs]] +} + +ad_proc -private xmlrpc::get_content {} { + There's no [ns_conn content] so this is a hack to get the content of the + XML-RPC request. Taken from ns_xmlrpc. + + @return string - the XML request + @author Dave Bauer +} { + # (taken from aol30/modules/tcl/form.tcl) + # Spool content into a temporary read/write file. + # ns_openexcl can fail, since tmpnam is known not to + # be thread/process safe. Hence spin till success + set fp "" + while {$fp == ""} { + set filename "[ns_tmpnam][clock clicks].xmlrpc2" + set fp [ns_openexcl $filename] + } + + fconfigure $fp -translation binary + ns_conncptofp $fp + close $fp + + set fp [open $filename r] + while {![eof $fp]} { + append text [read $fp] + } + close $fp + ns_unlink $filename + return $text +} + +ad_proc -private xmlrpc::fault { + code + msg +} { + Format a fault response to a XML-RPC request + + @param code error code (integer) + @param msg error message + + @return XML-RPC fault message +} { + # we could build this with the tDom commands, but it's quite a pain + # and I don't see the benefit for our simple needs - vinodk + set result " + + + + + + faultCode + $code + + + faultString + [ad_quotehtml $msg] + + + + + +" + + # now re-parse and then re-extract to make sure it's well formed + set doc [xml_parse -persist $result] + if { [catch {xml_doc_render $doc} result] } { + return -code error \ + "xmlrpc::fault XML is not well formed. error = $result" + } + xml_doc_free $doc + return $result +} + +ad_proc -public xmlrpc::register_proc { + proc_name +} { +

+ Register a proc to be available via XML-RPC. proc_name is + the name of a proc that is defined in the usual OpenACS way (i.e. ad_proc). + The proc_name is added to the xmlrpc_procs nsv array with a + value of 1. When an XML-RPC call comes in, this array is searched to see + if the proc_name has been registered. Currently, the presence of + proc_name in the nsv is enough to indicate + that the proc can be called via XML-RPC. At some point we may allow + administrators to disable procs, so we could set the value associated + with proc_name from 1 to 0. +

+ + @param proc_name Name of proc to be registered. + @return nothing +} { + nsv_set xmlrpc_procs $proc_name 1 +} + + +ad_proc -private xmlrpc::decode_value { + node +} { + Unpack the data in a value element. Most value elements will have a + subnode describing the datatype (e.g <string> or <int>). If no + subnode is present, then we should assume the value is a string. + + @param node <value> node that we're decoding + @return Returns the contents of the <value> node. If the value is + a <struct> then returns the data in a TCL array. If the value is an + <array> then returns the data in a TCL list. +} { + set result "" + if {[llength [xml_node_get_children $node]]} { + # subnode is specified + set subnode [xml_node_get_first_child $node] + set datatype [xml_node_get_name $subnode] + + switch -- $datatype { + string - + i4 - + int - + double - + base64 { + set result [xml_node_get_content $subnode] + } + + boolean { + set result [string is true [xml_node_get_content $subnode]] + } + + dateTime.iso8601 { + set result [clock scan [xml_node_get_content $subnode]] + } + + struct { + foreach member \ + [xml_node_get_children_by_name $subnode member] { + lappend result \ + [xml_node_get_content \ + [xml_node_get_children_by_name \ + $member name]] + lappend result \ + [xmlrpc::decode_value \ + [xml_node_get_children_by_name \ + $member value]] + } + } + + array { + foreach entry [xml_node_get_children \ + [xml_node_get_children_by_name \ + $subnode data]] { + lappend result [xmlrpc::decode_value $entry] + } + } + + default { + # we received a tag which is not a recognized datatype. + ns_log notice xmlrpc::decode_value ignored type: $datatype + } + } + } else { + # no datatype subnode, therefore it's a string + set result [xml_node_get_content $node] + } + return $result +} + +ad_proc -private xmlrpc::respond { + data +} { + Format a success response to an XML-RPC request + + @param data data to be returned to the client + @return data encoded in a properly formed XML-RPC response +} { + set result "" + append result [xmlrpc::construct {} $data] + append result "" + + # now re-parse and then re-extract to make sure it's well formed + set doc [xml_parse -persist $result] + if { [catch {xml_doc_render $doc} result] } { + return -code error \ + "xmlrpc::respond XML is not well formed. err = $result" + } + xml_doc_free $doc + return $result +} + +ad_proc -private xmlrpc::construct { + context + arglist +} { +

+ Construct an XML-RPC element. arglist is a 2-element list + which is converted to XML. The first element of arglist is + the datatype and the second element is the value. +

+ Example: +
+    set arglist {-int 33} 
+    set result [xmlrpc::construct {} $arglist]
+    set result ==> <i4>33</i4>
+    
+

+ This proc works recursively, so if your top level list has a list within + it, then that list will be processed first. The two examples of this are + arrays and structs. In addition, structs and arrays can contain each + other. +

+ Array example: +
+    set arglist {-array {
+        {-int 6682} 
+        {-boolean 0} 
+        {-text Iowa} 
+        {-double 8931.33333333} 
+        {-date {Fri Jan 01 05:41:30 EST 1904}}}}
+ 
+    set result [xmlrpc::construct {} $arglist]
+    set result ==>  <array>
+                    <data>
+                        <value>
+                            <i4>6682</i4>
+                        </value>
+                        <value>
+                            <boolean>0</boolean>
+                        </value>
+                        <value>
+                            <string>Iowa</string>
+                        </value>
+                        <value>
+                            <double>8931.33333333</double>
+                        </value>
+                        <value>
+                            <dateTime.iso8601>19040101T05:41:30</dateTime.iso8601>
+                        </value>
+                    </data>
+                </array>
+    
+

+ struct's have the special format: -struct {name1 {-datatype1 value1} name2 {-datatype2 value2}} +

+ Struct Example: +
+    set arglist {-struct {
+        ctLeftAngleBrackets {-int 5} 
+        ctRightAngleBrackets {-int 6} 
+        ctAmpersands {-int 7} 
+        ctApostrophes {-int 0} 
+        ctQuotes {-int 3}}}
+
+    set result [xmlrpc::construct {} $arglist]
+    set result ==>  <struct>
+                    <member>
+                        <name>ctLeftAngleBrackets</name>
+                        <value>
+                            <i4>5</i4>
+                        </value>
+                    </member>
+                    <member>
+                        <name>ctRightAngleBrackets</name>
+                        <value>
+                            <i4>6</i4>
+                        </value>
+                    </member>
+                    <member>
+                        <name>ctAmpersands</name>
+                        <value>
+                            <i4>7</i4>
+                        </value>
+                    </member>
+                    <member>
+                        <name>ctApostrophes</name>
+                        <value>
+                            <i4>0</i4>
+                        </value>
+                    </member>
+                    <member>
+                        <name>ctQuotes</name>
+                        <value>
+                            <i4>3</i4>
+                        </value>
+                    </member>
+                </struct>
+    
+

+ The context parameter is used internally to create tags within tags. +

+ Example: +
+    set arglist {-int 33}
+    set result [xmlrpc::construct {foo bar} $arglist]
+    set result ==> <foo><bar><i4>33</i4></bar></foo>
+    
+ + @param context extra tags to wrap around the data + @param arglist datatype-value list (or more complex types as described + above) + + @return XML formatted result +} { + set result "" + # list of valid options + set options_list [list "-string" "-text" "-i4" "-int" "-integer" \ + "-boolean" "-double" "-date" "-binary" "-base64" \ + "-variable" "-structvariable" "-struct" \ + "-array" "-keyvalue"] + + # if no valid option is specified, treat it as string + if {[lsearch $options_list [lindex $arglist 0]] == -1} { + set value "[ad_quotehtml $arglist]" + return [xmlrpc::create_context $context $arglist] + } + + if { [llength $arglist] % 2} { + # datatype required for each value + return -code error \ + "no value for option \"[lindex $arglist end]\"" + } + + foreach {option value} $arglist { + switch -- $option { + -string - + -text { + set value "[ad_quotehtml $value]" + append result [xmlrpc::create_context $context $value] + } + + -i4 - + -int - + -integer { + if {![string is integer $value]} { + return -code error \ + "value \"$value\" for option \"$option\" is not an integer:" + } + set value "$value" + append result [xmlrpc::create_context $context $value] + } + + -boolean { + set value "[string is true $value]" + append result [xmlrpc::create_context $context $value] + } + + -double { + if {![string is double $value]} { + return -code error \ + "value \"$value\" for option \"$option\" is not a floating point value" + } + set value "$value" + append result [xmlrpc::create_context $context $value] + } + + -date { + if {[catch {clock format [clock scan $value] \ + -format {%Y%m%dT%T} } datevalue]} { + return -code error \ + "value \"$value\" for option \"$option\" is not a valid date ($datevalue)" + } + + set value "$datevalue" + append result [xmlrpc::create_context $context $value] + } + + -binary - + -base64 { + # it is up to the application to do the encoding + # before the data gets here + set value "$value" + append result [xmlrpc::create_context $context $value] + } + + -array { + set data "" + foreach datum $value { + append data [xmlrpc::construct value $datum] + } + append data "" + append result [xmlrpc::create_context $context $data] + } + + -struct - + -keyvalue { + set data "" + foreach {name mvalue} $value { + append data "[ad_quotehtml $name]" + append data [xmlrpc::construct value $mvalue] + append data "" + } + append data "" + append result [xmlrpc::create_context $context $data] + } + + default { + # anything else will be ignored + ns_log notice xmlrpc::construct ignored option: $option \ + with value: $value + } + } + } + + return $result +} + +ad_proc -private xmlrpc::create_context { + context + value +} { + Return the value wrapped in appropriate context tags. If context is + a list of items, then the result will be wrapped in multiple tags. + Example: +
+    xmlrpc::create_context {param value} 78
+    returns ==> "78"
+    
+ + @param context context to create + @param value character data + @return string with value wrapped in context tags +} { + # reverse the list (algorithm from TCL Wiki) + set r_context {} + set i [llength $context] + while {$i} {lappend r_context [lindex $context [incr i -1]]} + + set result "$value" + foreach child_name $r_context { + set result "<$child_name>$result" + } + + return $result +} + +ad_proc -public xmlrpc::remote_call { + url + method + {args ""} +} { + Invoke a method on a remote server using XML-RPC + + @param url url of service + @param method method to call + @param args list of args to the method + + @return the response of the remote service. Error if remote service returns + a fault. +} { + set call "$method" + append call "" + if { [llength $args] } { + append call [xmlrpc::construct {param value} $args] + } + append call "" + + # now re-parse and then re-extract to make sure it's well formed + set doc [xml_parse -persist $call] + if { [catch {xml_doc_render $doc} request] } { + return -code error \ + "xmlrpc::fault XML is not well formed. error = $request" + } + xml_doc_free $doc + + # make the call + if {[catch {xmlrpc::httppost -url $url -content $request } response ]} { + ns_log error xmlrpc::remote_call \ + url: $url request: $request error: $response + return -code error [list HTTP_ERROR \ + "HTTP request failed due to \"$response\""] + } + return [xmlrpc::parse_response $response] +} + +ad_proc -private xmlrpc::httppost { + -url + {-timeout 30} + {-depth 0} + -content +} { + The proc util_httppost doesn't work for our needs. We need to send + Content-type of text/xml and we need to send a Host header. So, roll + our own XML-RPC HTTP POST. Wait - lars-blogger sends out XML-RPC pings + to weblogs.com. I'll steal the POST code from there and simplify that + call. + + @author Vinod Kurup +} { + if [catch { + if {[incr depth] > 10} { + return -code error "xmlrpc::httppost: Recursive redirection: $url" + } + set req_hdrs [ns_set create] + + # headers necesary for a post and the form variables + ns_set put $req_hdrs Accept "*/*" + ns_set put $req_hdrs User-Agent "[ns_info name]-Tcl/[ns_info version]" + ns_set put $req_hdrs "Content-type" "text/xml" + ns_set put $req_hdrs "Content-length" [string length $content] + + set http [ns_httpopen POST $url $req_hdrs 30 $content] + set rfd [lindex $http 0] + set wfd [lindex $http 1] + set rpset [lindex $http 2] + + flush $wfd + close $wfd + + set headers $rpset + set response [ns_set name $headers] + set status [lindex $response 1] + + # follow 302 + if {$status == 302} { + set location [ns_set iget $headers location] + if {$location != ""} { + ns_set free $headers + close $rfd + set page [xmlrpc::httppost -url $location \ + -timeout $timeout -depth $depth -content $content] + } + } else { + set length [ns_set iget $headers content-length] + if [string match "" $length] {set length -1} + set err [catch { + while 1 { + set buf [_ns_http_read $timeout $rfd $length] + append page $buf + if [string match "" $buf] break + if {$length > 0} { + incr length -[string length $buf] + if {$length <= 0} break + } + } + } errMsg] + ns_set free $headers + close $rfd + if $err { + global errorInfo + return -code error -errorinfo $errorInfo $errMsg + } + } + } errmsg ] { + ns_log warning "xmlrpc::httppost error: $errmsg" + return -1 + } else { + return $page + } +} + +ad_proc -private xmlrpc::parse_response {xml} { + Parse the response from a XML-RPC call. + + @param xml the XML response + @return result +} { + set doc [xml_parse -persist $xml] + set root [xml_doc_get_first_node $doc] + + if { ![string equal [xml_node_get_name $root] "methodResponse"] } { + set root_name [xml_node_get_name $root] + xml_doc_free $doc + return -code error "xmlrpc::parse_response: invalid server reponse - root node is not methodResponse. it's $root_name" + } + + set node [xml_node_get_first_child $root] + switch -- [xml_node_get_name $node] { + params { + # need more error checking here. + # if the response is not well formed, we'll probably + # get an error, but it may be hard to track down + set param [xml_node_get_first_child $node] + set value [xml_node_get_first_child $param] + set result [xmlrpc::decode_value $value] + } + fault { + # should do more checking here... + array set fault [xmlrpc::decode_value \ + [xml_node_get_first_child $node]] + xml_doc_free $doc + return -code error -errorcode $fault(faultCode) $fault(faultString) + } + default { + set type [xml_node_get_name $node] + xml_doc_free $doc + return -code error "xmlrpc::parse_response: invalid server response ($type)" + } + } + xml_doc_free $doc + + return $result +} + +ad_proc -private xmlrpc::invoke { + xml +} { + Take the XML-RPC request and invoke the method on the server. + The methodName element contains the Tcl procedure to evaluate. The + method is called from the global stack level. + + @param xml XML-RPC data from the client + @return result encoded in XML and ready for return to the client +} { + # check that the XML-RPC Server is enabled + if { ![xmlrpc::enabled_p] } { + set result [xmlrpc::fault 3 "XML-RPC Server disabled"] + ns_log error "xmlrpc::invoke fault $result" + return $result + } + + ns_log debug "xmlrpc::invoke REQUEST: $xml" + if {[catch {set doc [xml_parse -persist $xml]} err_msg]} { + set result [xmlrpc::fault 1 "error parsing request: $err_msg"] + ns_log error "xmlrpc::invoke: error parsing request: $err_msg" + } else { + # parse OK - get data + set data [xml_doc_get_first_node $doc] + + set method_name \ + [xml_node_get_content \ + [lindex \ + [xml_node_get_children_by_name $data methodName] 0 ]] + + set arguments [list] + set params [xml_node_get_children_by_name $data params] + foreach parameter [xml_node_get_children_by_name $params param] { + lappend arguments \ + [xmlrpc::decode_value [xml_node_get_first_child $parameter]] + } + + set errno [catch {xmlrpc::invoke_method $method_name $arguments} result] + if { $errno } { + set result [xmlrpc::fault $errno $result] + ns_log error "xmlrpc_invoke: error in xmlrpc method REQUEST: $xml RESULT: $result" + } else { + # success + set result [xmlrpc::respond $result] + ns_log debug "xmlrpc::invoke result $result" + } + } + xml_doc_free $doc + + return $result +} + +ad_proc -private xmlrpc::invoke_method { + method_name + arguments +} { + Call the given method on the OpenACS server. It's up to the caller + to catch any error that we get. + + @param method_name methodName from XML-RPC + @param arguments list of arguments + @return result of the OpenACS proc + @author Vinod Kurup +} { + # check that the method is registered as a valid XML-RPC method + if {![nsv_exists xmlrpc_procs $method_name]} { + return -code error -errorcode 2 "methodName $method_name doesn't exist" + } + ns_log debug "xmlrpc::invoke_method method $method_name args $arguments" + set result [uplevel #0 [list $method_name] $arguments] + return $result +} Index: openacs-4/packages/xml-rpc/tcl/test/xml-rpc-test-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/tcl/test/xml-rpc-test-procs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/tcl/test/xml-rpc-test-procs.tcl 26 Nov 2003 02:59:14 -0000 1.1 @@ -0,0 +1,181 @@ +# /packages/xml-rpc/tcl/test/xml-rpc-test-procs.tcl +ad_library { + Test the XML-RPC interface + @author Vinod Kurup [vinod@kurup.com] + @creation-date Sat Oct 25 10:49:55 2003 + @cvs-id $Id: xml-rpc-test-procs.tcl,v 1.1 2003/11/26 02:59:14 vinodk Exp $ +} + +aa_register_case -cats script xml_rpc_mounted { + Test to make sure the xml-rpc package has been mounted +} { + aa_run_with_teardown -rollback -test_code { + aa_false "XML-RPC url not null" [empty_string_p [xmlrpc::url]] + } +} + +aa_register_case -cats script xml_rpc_fault { + Test the fault generation code +} { + set expected_code 22 + set expected_string "my error message with html codes" + + aa_run_with_teardown -rollback -test_code { + set result [xmlrpc::fault $expected_code $expected_string] + + # extract faultCode and faultString + set doc [xml_parse -persist $result] + set value_node [xml_node_get_first_child [xml_node_get_first_child [xml_doc_get_first_node $doc]]] + array set fault [xmlrpc::decode_value $value_node] + xml_doc_free $doc + + aa_equals "Proper faultCode" $fault(faultCode) $expected_code + aa_equals "Proper faultString" $fault(faultString) $expected_string + } +} + +ad_proc -private xmlrpc_decode_test_prep { value } { + Takes the contents of a <value> node, calls xmlrpc::decode_value and + returns the result. This is done repeatedly in the xml_rpc_decode_value + test, so I broke it out into a separate function for that purpose +} { + set doc [xml_parse -persist "$value"] + set result [xmlrpc::decode_value [xml_doc_get_first_node $doc]] + xml_doc_free $doc + return $result + +} + +aa_register_case -cats script xml_rpc_decode_value { + Test xmlrpc::decode_value to be sure it decodes properly +} { + aa_run_with_teardown -rollback -test_code { + set result [xmlrpc_decode_test_prep "a string"] + aa_equals "string test" $result "a string" + + set result [xmlrpc_decode_test_prep "- a naked string"] + aa_equals "naked string test" $result "- a naked string" + + set result [xmlrpc_decode_test_prep "22"] + aa_equals "int test" $result 22 + + set result [xmlrpc_decode_test_prep "33"] + aa_equals "i4 test" $result 33 + + set result [xmlrpc_decode_test_prep "3.1415"] + aa_equals "double test" $result 3.1415 + + set result [xmlrpc_decode_test_prep "1"] + aa_equals "boolean test 1" $result 1 + + set result [xmlrpc_decode_test_prep "f"] + aa_equals "boolean test 2" $result 0 + + set result [xmlrpc_decode_test_prep "20030821T083122"] + aa_equals "date test" $result 1061469082 + + + unset result + array set result [xmlrpc_decode_test_prep "id19contentMy content"] + aa_equals "struct test 1" $result(id) 19 + aa_equals "struct test 2" $result(content) "My content" + + unset result + set result [xmlrpc_decode_test_prep "phrase 12nd phrasefinal phrase"] + aa_equals "array test 1" [lindex $result 0] "phrase 1" + aa_equals "array test 2" [lindex $result 1] "2nd phrase" + aa_equals "array test 3" [lindex $result 2] "final phrase" + + unset result + set result [xmlrpc_decode_test_prep "phrase 1sublistGot it!"] + array set struct [lindex $result 1] + aa_equals "array inside struct inside array" [lindex $struct(sublist) 0] "Got it!" + } +} + +aa_register_case -cats script xml_rpc_respond { + Test the response generation code +} { + set expected_data "my data" + + aa_run_with_teardown -rollback -test_code { + set result [xmlrpc::respond $expected_data] + + # extract data + set doc [xml_parse -persist $result] + set value_node [xml_node_get_first_child [xml_node_get_first_child [xml_node_get_first_child [xml_doc_get_first_node $doc]]]] + set data [xmlrpc::decode_value $value_node] + xml_doc_free $doc + + aa_equals "Proper data" $data $expected_data + } +} + +aa_register_case -cats script xml_rpc_construct { + Test the construction code +} { + + aa_run_with_teardown -rollback -test_code { + # use testcases from the ad_proc documentation + + # int test + set arglist {-int 33} + set result [xmlrpc::construct {} $arglist] + aa_equals "int contruction" $result "33" + + # array test + set arglist {-array { + {-int 6682} + {-boolean 0} + {-text Iowa} + {-double 8931.33333333} + {-date {Fri Jan 01 05:41:30 EST 1904}}}} + + set result [xmlrpc::construct {} $arglist] + aa_equals "array construction" $result "66820Iowa8931.3333333319040101T05:41:30" + + # struct test + set arglist {-struct { + ctLeftAngleBrackets {-int 5} + ctRightAngleBrackets {-int 6} + ctAmpersands {-int 7} + ctApostrophes {-int 0} + ctQuotes {-int 3}}} + + set result [xmlrpc::construct {} $arglist] + aa_equals "struct test" $result "ctLeftAngleBrackets5ctRightAngleBrackets6ctAmpersands7ctApostrophes0ctQuotes3" + } + + # test context parameter + set arglist {-int 33} + set result [xmlrpc::construct "foo bar" $arglist] + aa_equals "context test" $result "33" + +} + +aa_register_case -cats web xml_rpc_validate { + Test the standard XML-RPC validation suite +} { + + # run the validation suite specified in validator-procs.tcl + # if those procs change, this proc needs to change too + set test_list \ + [list \ + arrayOfStructsTest 6 \ + countTheEntities {ctLeftAngleBrackets 4 ctRightAngleBrackets 4 ctAmpersands 9 ctApostrophes 7 ctQuotes 1} \ + easyStructTest 6 \ + echoStructTest {bob 5} \ + manyTypesTest {1 0 wazzup 3.14159 994261830 R0lGODlhFgASAJEAAP/////OnM7O/wAAACH5BAEAAAAALAAAAAAWABIAAAJAhI+py40zDIzujEDBzW0n74AaFGChqZUYylyYq7ILXJJ1BU95l6r23RrRYhyL5jiJAT/Ink8WTPoqHx31im0UAAA7} \ + moderateSizeArrayCheck {WisconsinNew York} \ + nestedStructTest 7 \ + simpleStructReturnTest {times1000 2000 times100 200 times10 20} + ] + set url [ad_url][xmlrpc::url] + + aa_run_with_teardown -rollback -test_code { + foreach {test_name expected} $test_list { + set result [validate1.$test_name $url] + aa_equals $test_name $result $expected + } + } +} Index: openacs-4/packages/xml-rpc/www/index.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/www/index.adp,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/www/index.adp 26 Nov 2003 02:59:14 -0000 1.1 @@ -0,0 +1,10 @@ + + +

+This is the URL from which XML-RPC Requests will be handled. +

+ +

+Perhaps you want the Admin Pages or the Documentation. +

Index: openacs-4/packages/xml-rpc/www/index.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/www/index.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/www/index.tcl 26 Nov 2003 02:59:14 -0000 1.1 @@ -0,0 +1,18 @@ +# /packages/xml-rpc/www/index.tcl +ad_page_contract { + Accept XML-RPC POST requests and processes them. GET requests are shown + links to the admin pages or docs. + + @author Vinod Kurup [vinod@kurup.com] + @creation-date Mon Sep 29 23:35:14 2003 + @cvs-id $Id: index.tcl,v 1.1 2003/11/26 02:59:14 vinodk Exp $ +} { +} + +if {[string equal [ns_conn method] POST]} { + set content [xmlrpc::get_content] + ns_return 200 text/xml [xmlrpc::invoke $content] + return +} + +# GET requests fall through to index.adp Index: openacs-4/packages/xml-rpc/www/admin/index.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/www/admin/index.adp,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/www/admin/index.adp 26 Nov 2003 02:59:14 -0000 1.1 @@ -0,0 +1,33 @@ + +XML-RPC Administration + + + + + + + + + + + + +
XML-RPC URL:@rpc_url@
Status: + EnabledDisabled +
+ +

+The following procedures are registered: +

+ + + + + + + + + + + +
Proc Name Enabled?
@rpc_procs.name;noquote@ @rpc_procs.enabled_p@
Index: openacs-4/packages/xml-rpc/www/admin/index.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/www/admin/index.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/www/admin/index.tcl 26 Nov 2003 02:59:14 -0000 1.1 @@ -0,0 +1,28 @@ +# /packages/xml-rpc/www/admin/index.tcl +ad_page_contract { + Front page of admin + @author Vinod Kurup [vinod@kurup.com] + @creation-date Thu Oct 9 15:22:41 2003 + @cvs-id $Id: index.tcl,v 1.1 2003/11/26 02:59:14 vinodk Exp $ +} { +} -properties { + rpc_url:onevalue + server_enabled_p:onevalue + rpc_procs:multirow +} + +set rpc_url [ad_url][xmlrpc::url] +set server_enabled_p [xmlrpc::enabled_p] + +multirow create rpc_procs name enabled_p + +foreach proc_name [xmlrpc::list_methods] { + if { $server_enabled_p } { + set enabled_p [ad_decode [nsv_get xmlrpc_procs $proc_name] 0 No Yes] + } else { + set enabled_p No + } + + set proc_name [api_proc_link $proc_name] + multirow append rpc_procs $proc_name $enabled_p +} Index: openacs-4/packages/xml-rpc/www/admin/toggle.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/www/admin/toggle.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/www/admin/toggle.tcl 26 Nov 2003 02:59:14 -0000 1.1 @@ -0,0 +1,15 @@ +# /packages/xml-rpc/www/admin/toggle.tcl +ad_page_contract { + Toggle the server status + @author Vinod Kurup [vinod@kurup.com] + @creation-date Sat Oct 11 01:10:06 2003 + @cvs-id $Id: toggle.tcl,v 1.1 2003/11/26 02:59:14 vinodk Exp $ +} { +} + +parameter::set_from_package_key \ + -package_key xml-rpc \ + -parameter EnableXMLRPCServer \ + -value [string is false [xmlrpc::enabled_p]] + +ad_returnredirect ./ \ No newline at end of file Index: openacs-4/packages/xml-rpc/www/doc/index.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xml-rpc/www/doc/index.html,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xml-rpc/www/doc/index.html 26 Nov 2003 02:59:14 -0000 1.1 @@ -0,0 +1,297 @@ + + +XML-RPC + + +

XML-RPC Server Documentation

+
+

Overview

+ +
+

+XML-RPC is a protocol which allows clients to make remote procedure calls +on your server. Data is transformed into a standard XML format before being +transferred between the client and server. This allows software using +different OS's and programming languages to interact. See XML-RPC.com for more information. +

+
+ +

Why do we need it in OpenACS?

+ +
+

+Some XML-RPC protocols have become popular in the web world. The Blogger API and +the Metaweblog API allow +users to manage their blogs using tools of their choice and these have +become widespread enough that users expect to find this functionality in +any blogging software. For this reason, it's important to provide this +minimum of functionality. +

+
+ +

User Documentation

+ +
+

+There are no user-facing pages. XML-RPC client software may require the +user to know the URL that is accepting XML-RPC requests, which is +admin-definable (default: http://example.com/RPC2/). XML-RPC savvy users +can call the XML-RPC method system.listMethods to see which +methods the server supports. +

+
+ +

Admin Documentation

+ +
+

+The server is installed by default at /RPC2/. Administrators can change +this by unmounting the package and remounting it at the desired URL. The +server can be disabled or enabled via the /admin pages. +

+ +

+The XML-RPC folks have defined a standard validation suite. These tests are +implemented in the Automated Testing package, so admins can test their +server against this suite (locally) by running all the automated +tests. They can also go to http://validator.xmlrpc.com to test their site's +validity (remotely) from there. +

+
+ +

Developer Documentation

+ +
+

Adding XML-RPC support to your package

+ +
+

+The first thing you need to do is write the methods that you want to be +available via XML-RPC. They should be defined as ad_procs just as any other +OpenACS procs except for 2 differences. + +

    +
  1. + They need to be able to accept arguments as sent to them + from xmlrpc::decode + +

    + In XML-RPC, every value has a datatype. Since TCL is a weakly-typed + language, we could care less about the datatype (for the most part). So + for scalar values (int, boolean, string, double, dateTime.iso8601, + base64), xmlrpc::decode simply sends along the value to your + proc. To recap, for scalar values, you need to do nothing + special. For the 2 complex types (structs and arrays), the + values are sent to your proc as TCL structures - XML-RPC structs are sent + TCL arrays and XML-RPC arrays are sent as TCL lists. For example, if your + proc expects a struct with 3 members (name, address and phone), then this + is how the beginning of your proc will look. + +

    +  array set user_info $struct
    +  set name $user_info(name)
    +  set address $user_info(address)
    +  set phone $user_info(phone)
    +  
    + + Or if your proc expects an array with n integers, which it then sums, + then this is how your proc will look. + +
    +  foreach num $array {
    +      incr sum $num
    +  }
    +  
    + +

    +
  2. + +
  3. + They need to be able to return data that xmlrpc::respond + will be able to translate to XML. +

    + Scalar data should be returned as a 2 item list {-datatype value}. So if + your proc returns an int, its last statement might be: +

    + +
    +  return [list -int $result]
    +  
    + + Returning complex data structures (struct, array) is a little more *ahem* + complex. One of the confusing things is the terminology. As I noted + above, XML-RPC arrays are equivalent to TCL lists and XML-RPC structs are + equivalent to TCL arrays. The other confusing thing is that XML-RPC is + strongly typed and TCL isn't, so when you're converting from TCL to + XML-RPC, you need to add the datatype for each scalar value. +
      +
    • Returning an array of mixed type +
      +    return [list -array [list [list -int 36] [list -string "foo"]]]
      +    
      +
    • + +
    • Returning a struct (foo=22, bar=blah) +
      +    return [list -struct [list foo [list -int 22] bar [list -string blah]]]
      +    
      +
    • + +
    • Returning the above struct using a TCL array +
      +    set my_struct(foo) [list -int 22]
      +    set my_struct(bar) [list -string blah]
      +    return [list -struct [array get my_struct]]
      +    
      +
    • + +
    • Returning an array of structs +
      +    set user1(name) {-string "George Bush"} 
      +    set user1(id) {-int 41}
      +    set user2(name) {-string "Bill Clinton"} 
      +    set user2(id) {-int 42}
      +    return [list -array [list 
      +                             [list -struct [array get user1]] 
      +                             [list -struct [array get user2]]]]
      +    
      +
    • +
    + +
  4. +
+

+ +

+Once your procs are defined in packagekey/tcl/foo-procs.tcl, register them +in packagekey/tcl/foo-init.tcl. The *-init.tcl files are loaded after all +the *-procs.tcl files have been loaded, so xmlrpc::register_proc will be +available if the xmlrpc package is installed. Make sure you add the xmlrpc +package as a dependency of your package if you register any XML-RPC procs. +If you don't want your package to depend on xmlrpc, you can test for the +existence of the xmlrpc_procs nsv before calling xmlrpc::register_proc +

+ +

+This registers 'system.listMethods' +

+xmlrpc::register_proc system.listMethods
+
+

+
+ +

Implementation details

+ +
+

+Here is the sequence of events in an XML-RPC call. See the documentation for each proc for more details. +

    +
  1. A POST request is made to your XML-RPC URL.
  2. +
  3. The xmlrpc::get_content proc grabs the content of the + POST request. This is a bit of a hack to cover the fact that there is + no ns_conn content proc.
  4. +
  5. xmlrpc::invoke is called to process the XML + request.
  6. +
  7. If the server is disabled, a fault is returned
  8. +
  9. The XML is parsed for the methodName and + arguments. xmlrpc::decode_value decodes the XML-RPC + params into TCL variables.
  10. +
  11. xmlrpc::invoke_method checks to be sure the method is + registered and then attempts to call the OpenACS proc
  12. +
  13. xmlrpc::invoke catches any errors from this attempt and + creates a XML-RPC fault to return to the client if so. If there was + no error, then xmlrpc::respond is called to format the + result as a XML-RPC response.
  14. +
  15. xmlrpc::construct does the heavy work of converting the + TCL results back into valid XML-RPC params
  16. +
  17. Finally, if no errors occur in this process, the result is returned + to the client as text/xml
  18. +
+ +

+More details are provided in the ad_proc documentation for each proc. +

+
+
+ +

XML-RPC client

+ +
+

+This package also implements a simple XML-RPC client. Any package that +needs to make XML-RPC calls can simply add a dependency to this package and +then call xmlrpc::remote_call. As an example, the +system.add method sums a variable number of ints. To call the +system.add method on http://example.com/RPC2, do this: +

+catch {xmlrpc::remote_call http://example.com/RPC2 system.add -int 4 -int 44 -int 23} result
+set result ==> 71
+
+ +

+It's important to always catch outgoing +XML-RPC calls. If there's an error, it will be written to the catch +variable (result in the example above). If there's no error, +then the return value will be in result. +

+ +

+Implementation detail: The client needs to be able to POST +requests to other servers. The util_httppost proc in +acs-tcl/tcl/utilities-procs.tcl doesn't work because it doesn't let you +specify the Content-Type, which needs to be text/xml, and it doesn't add +Host headers, which are required if the server you're POSTing to is using +virtual hosting. So, this package implemements its own HTTP POST proc +(which was stolen from lars-blogger's weblogs.com XML-RPC ping). +

+ + +
+ +

History of XML-RPC in OpenACS

+ +
+

+The first implementation of XML-RPC for AOLServer was ns_xmlrpc, whose credits +state: +

+ Ns_xml conversion by Dave Bauer (dave at thedesignexperience.org) with + help from Jerry Asher (jerry at theashergroup.com). This code is based + on the original Tcl-RPC by Steve Ball with contributions by Aaron + Swartz. The original Tcl-RPC uses TclXML and TclDOM to parse the + XML. It works fine but since OpenACS-4 will use ns_xml I converted it. +
+

+ +

+I took this version and converted it into a OpenACS service package. All of +the xml procs now use the XML abstraction procs inside acs-tcl (which +currently use tDOM). All the procs are in a xmlrpc:: namespace and +documentation has been added. I added support for some standard XML-RPC +reserved procs (system.listMethods, system.methodHelp, system.multicall). I +changed the semantics slightly in one area. XML-RPC arrays were being +converted to TCL arrays, with the name of each item being an integer +index. I thought it made more sense to make these TCL lists (since that is +what a TCL list is anyways). It makes the code more consistent and makes it +easier to understand how to deal with XML-RPC datatypes. +

+XML-RPC struct = TCL array.
+XML-RPC array = TCL list. +
+

+
+ +

ChangeLog

+
+
    +
  • First revision - 2003-10-13 - Vinod Kurup
  • +
  • Validation tests now implemented via automated-testing - 2003-11-01
  • +
+
+ +
+
Vinod Kurup
+ + \ No newline at end of file