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/Its very close, but I need the SOAP above envelope to look like this working version (both are very close):" 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>
<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
