package require SOAP package require rpcvar namespace import -force rpcvar::typedef typedef { Username DATA Password DATA Nonce DATA Created DATA RemoteUser DATA RemoteClient DATA RemoteAddress DATA EnvironmentalData DATA SessionID DATA } authentication typedef { Authentication authentication ApplicationType DATA RequestID DATA WebUserID DATA WebUserName DATA PlanID DATA EmployerID DATA MemberID DATA EntityType MEMBER EntityReferenceNumber DATA Password DATA EncryptionMethod PLAINTEXT InputMethodEditor TELEPHONE } webUser SOAP::create Test -proxy "http://localhost/superSOAP/webcontrol.asmx" \ -action "http://www.atune.biz/web/services/WC/2006-01/isWebUserAuthenticated" \ -uri "http://www.atune.biz/web/services/WC/2006-01" \ -params {WebUser webUser}When the soap method, isWebUserAuthenticated, is called it generates this as the SOAP request :
<?xml version="1.0"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP- ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"> <SOAP-ENV:Body> <ns:isWebUserAuthenticated xmlns:ns="http://www.atune.biz/web/services/WC/2006-01"> <WebUser> <Authentication> <Username>IVRUSER</Username> <Password>newPassword</Password> <Nonce></Nonce> <Created></Created> <RemoteUser>TESTER</RemoteUser> <RemoteClient>SomeCompany</RemoteClient> <RemoteAddress>127.0.0.1</RemoteAddress> <EnvironmentalData></EnvironmentalData> <SessionID></SessionID> </Authentication> <ApplicationType>MOL</ApplicationType> <RequestID></RequestID> <WebUserID></WebUserID> <WebUserName>11111</WebUserName> <PlanID>RS</PlanID> <EmployerID>-1</EmployerID> <MemberID>-1</MemberID> <EntityType>MEMBER</EntityType> <EntityReferenceNumber>0</EntityReferenceNumber> <Password>5555</Password> <EncryptionMethod>PLAINTEXT</EncryptionMethod> <InputMethodEditor>TELEPHONE</InputMethodEditor> </WebUser> </ns:isWebUserAuthenticated> </SOAP-ENV:Body> </SOAP-ENV:Envelope>Its very close, but I need the SOAP above envelope to look like this working version (both are very close):
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Body> <isWebUserAuthenticated xmlns="http://www.atune.biz/web/services/WC/2006-01"> <WebUser> <Authentication Enabled="1"> <Username>IVRUSER</Username> <Password Type="PLAINTEXT" Token="*NONE*">newPassword</Password> <Nonce></Nonce> <Created>20060309T080101</Created> <RemoteUser>TESTER</RemoteUser> <RemoteClient>SomeCompany</RemoteClient> <RemoteAddress>127.0.0.1</RemoteAddress> <EnvironmentData></EnvironmentData> <SessionID></SessionID> </Authentication> <ApplicationType>MOL</ApplicationType> <RequestID></RequestID> <WebUserID></WebUserID> <WebUserName>157014233</WebUserName> <PlanID>RS</PlanID> <EmployerID>-1</EmployerID> <MemberID>-1</MemberID> <EntityType>MEMBER</EntityType> <EntityReferenceNumber>0</EntityReferenceNumber> <Password>5555</Password> <EncryptionMethod>PLAINTEXT</EncryptionMethod> <InputMethodEditor>TELEPHONE</InputMethodEditor> </WebUser> </isWebUserAuthenticated> </soap:Body> </soap:Envelope>Any ideas what I need to change in my TclSOAP command calls to make the SOAP envelope look like the latter XML above? Its currently generating the first XML. I notice a couple of things different: The latter has an Enabled="1" attribute and the password tag has some more attributes too, and its not using the /ns: namespace like the first. The latter envelop works and returns a result the first request returns errors. The other thing thats differnt is the second version is using simpler looking tags like soap:Evelope and soap:Body. So, how do I add the XML attributes needed by the webservice, etc?Thanks in advance. snichols.
lexfiend: It doesn't directly answer your question, but since you already know exactly what the SOAP request must look like, you might want to consider the XML-template method I devised and documented in WSDL. I've found it to be more cost-effective (read: less time + hair-pulling) than trying to tweak my code to coax TclSOAP to satisfy different Web services (and possibly having to re-tweak it for newer releases of TclSOAP).
snichols Thanks. I saw your example in the WSDL page in the Wiki. It is a very quick fix, but I don't know if its the best way since you are not using an XML Tcl package to do your replacing. There's a potential for XML special characters in the values you are replacing in your XML template. I don't know how likely this is, and if it does fail what's the outcome: some user somewhere that has to use a different method for support. I may end up using what you did. Thanks again.
snichols Mar 14th 2006 Well, I was able to get the SOAP envelop built in a way the web service understood. Below is the complete working Tcl code. Basically, all I had to do was change the name of the root xmlns attributes from SOAP-ENV to soap and remove the body xmlns ns attribue and it worked. It even works with out setting the body attributes. I'm happy with this outcome because I was hoping to reuse the Tcl SOAP API as much as possible. Would someone mind providing an example of how to edit a SOAP body element attribute? I wasn't succesful in edit the attributes using XPath I had to use next child, next sibling to edit them. Is this normal?
package require SOAP package require rpcvar package require tdom namespace import -force rpcvar::typedef # Create two complex data types: Authentication and Web User # These are required by the web service. typedef { Username DATA Password DATA Nonce DATA Created DATA RemoteUser DATA RemoteClient DATA RemoteAddress DATA EnvironmentData DATA SessionID DATA } authentication typedef { Authentication authentication ApplicationType DATA RequestID DATA WebUserID DATA WebUserName DATA PlanID DATA EmployerID DATA MemberID DATA EntityType MEMBER EntityReferenceNumber DATA Password DATA EncryptionMethod PLAINTEXT InputMethodEditor TELEPHONE } webUser # Custom Authentication XML Envelope Procedure. # This was needed for the soap namespaces and removing the ns namespace proc ::SOAP::AuthenticationWrapper {procVarName args} { upvar $procVarName procvar set procName [lindex [split $procVarName {_}] end] set params $procvar(params) set name $procvar(name) set uri $procvar(uri) set soapenv $procvar(version) set soapenc $procvar(encoding) # Check for options (ie: -header) give up on the fist non-matching arg. array set opts {-headers {} -attributes {}} while {[string match -* [lindex $args 0]]} { switch -glob -- [lindex $args 0] { -header* { set opts(-headers) [concat $opts(-headers) [lindex $args 1]] set args [lreplace $args 0 0] } -attr* { set opts(-attributes) [concat $opts(-attributes) [lindex $args 1]] set args [lreplace $args 0 0] } -- { set args [lreplace $args 0 0] break } default { # stop option processing at the first invalid option. break } } set args [lreplace $args 0 0] } # check for variable number of params and set the num required. if {[lindex $params end] == "args"} { set n_params [expr {( [llength $params] - 1 ) / 2}] } else { set n_params [expr {[llength $params] / 2}] } # check we have the correct number of parameters supplied. if {[llength $args] < $n_params} { set msg "wrong # args: should be \"$procName" foreach { id type } $params { append msg " " $id } append msg "\"" return -code error $msg } set doc [dom::DOMImplementation create] set envx [dom::document createElement $doc "soap:Envelope"] dom::element setAttribute $envx "xmlns:soap" $soapenv dom::element setAttribute $envx "xmlns:SOAP-ENC" $soapenc dom::element setAttribute $envx "soap:encodingStyle" $soapenc # The set of namespaces depends upon the SOAP encoding as specified by # the encoding option and the user specified set of relevant schemas. foreach {nsname url} [concat \ [rpcvar::default_schemas $soapenc] \ $procvar(schemas)] { if {! [string match "xmlns:*" $nsname]} { set nsname "xmlns:$nsname" } dom::element setAttribute $envx $nsname $url } # Insert the Header elements (if any) if {$opts(-headers) != {}} { set headelt [dom::document createElement $envx "soap:Header"] foreach {hname hvalue} $opts(-headers) { set hnode [dom::document createElement $headelt $hname] insert_value $hnode $hvalue } } # Insert the body element and atributes. set bod [dom::document createElement $envx "soap:Body"] if {$uri == ""} { # don't use a namespace prefix if we don't have a namespace. set cmd [dom::document createElement $bod "$name" ] } else { set cmd [dom::document createElement $bod "$name" ] dom::element setAttribute $cmd "xmlns" $uri } # Insert any method attributes if {$opts(-attributes) != {}} { foreach {atname atvalue} $opts(-attributes) { dom::element setAttribute $cmd $atname $atvalue } } # insert the parameters. set param_no 0 foreach {key type} $params { set val [lindex $args $param_no] set d_param [dom::document createElement $cmd $key] insert_value $d_param [rpcvar $type $val] incr param_no } # We have to strip out the DOCTYPE element though. It would be better to # remove the DOM node for this, but that didn't work. set prereq [dom::DOMImplementation serialize $doc] set req {} dom::DOMImplementation destroy $doc ;# clean up regsub "<!DOCTYPE\[^>\]*>\r?\n?" $prereq {} req ;# hack set req [encoding convertto utf-8 $req] ;# make it UTF-8 return $req ;# return the XML data } SOAP::create isWebUserAuthenticated -proxy "http://localhost/superSOAP/webcontrol.asmx" \ -action "http://www.atune.biz/web/services/WC/2006-01/isWebUserAuthenticated" \ -uri "http://www.atune.biz/web/services/WC/2006-01" \ -params {WebUser webUser} SOAP::configure isWebUserAuthenticated -wrapProc ::SOAP::AuthenticationWrapper