TclSOAP

TclSOAP, a Tcl package, provides method binding for Tcl clients to remote procedures and web services implemented using either SOAP, XML-RPC or JSON-RPC. The first two protocols package up a payload as XML and use HTTP to transport the data to and from the service provider. The last formats the payload as JSON. TclSOAP provides a means of binding Tcl procedures to the remote method and for creating RPC servers easily under tclhttpd.

Attributes

current version
1.6.7

Download

http://sourceforge.net/projects/tclsoap/files/

dom patch
DcK 2013-01-05: patch for compatibility with recent dom packages.
ActiveTcl
includes TclSOAP

Dependencies

http
TclDOM
tcllib

For versions greater than 1.6.1 you'll need tcllib 1.2. If you want to use other transports then you'll need the relevant package (smtp and ftp from tcllib or beepcore-tcl).

Documentation

http://tclsoap.sourceforge.net

Development

The project is hosted on SourceForge at https://sourceforge.net/projects/tclsoap/

Description

TclSOAP also supports XML-RPC.

APN 2008-07-05: TclSOAP now includes client side support for JSON-RPC.

PT 2003-06-26: As of version 1.6.6 TclSOAP supports SOAP1.1 and makes some attempts towards supporting SOAP1.2. The SOAP methods have a -version option which can be set to SOAP1.1 or SOAP1.2 or explicitly to the xml namespace required. You can also set the encoding in use using the -encoding option. Currently I'm not planning much work towards full 1.2 compliance.

See Also

Googling with SOAP
a simple accessor for the Google API using TclSOAP

Change Log

2003-01-25
TclSOAP 1.6.6 released

SSL

TclSOAP and SSL (Secure Sockets Layer) , David Bleicher, 2002

Ectoraige: This may be out of date, the process Bleicher described didn't work for me anyway - I think for versions past 1.6, then all you should have to do to get https urls working is:

package require SOAP::https

If not you may find it worthwhile looking at [L1 ]. Hi - this link to sourceforge.net is broken - do you know the current status of doing SOAP with HTTPS?

WSDL

WSDL is an XML dialect for describing services which are implemented with SOAP.

PT: as of 1.6.5 I've got some WSDL support in the development branch. Hopefully this will get merged into the main package before too long (say 2003-04?). The basic structure is likely to be that calling

SOAP::service http://tclsoap.sf.net/WSDL/silab.wsdl
SOAP::service file:///usr/local/etc/WSDL/interop.wsdl

will parse the web service description and create a namespace will all the methods defined. For the interop suite that would be interopLab::echoString and interopLab::echoInteger etc.

As of 2003-01-22, the WSDL parsing is done but I have yet to process the type descriptions (which pretty much involves parsing XML Schema language :( ).

lexfiend 2006-02-13: In the meantime, for Tcl'ers who need to use TclSOAP with complex WSDL files, see my note in WSDL for an interim method of dealing with this situation.

WS-Security

DKF: Does TclSOAP support WS-Security?

[PT]: Not at the moment and perhaps never. I don't have any pressing need to support this extension and it isn't expecially interesting to do. I tend to be interested in communications generally (hence the SOAP and smtpd work). Of course, some sort of financial incentive will guarantee a rapid change of heart and ensure a place at the top of my work pile!!

DKF: Rats! Might have to do it myself sometime. (I hate writing security code!)

tsoap

There is a tDOM based package called tsoap which implements the same api as TclSOAP, but uses tDom instead of TclDom.

cvs.openacs.org

WSDL Generator

Byron Whitlock has a nice WSDL generator for Tclsoap and tclhttpd.

Lost

The following things are lost in the fogs of time.


Pat Thoyts, on the chat, writes: Someone has done a tclsoap wrapper for the google API. it is here http://gondolin.hist.liv.ac.uk/~cheshire/tclgoogle.html

Examples

The documentation at http://tclsoap.sourceforge.net gives pretty extensive examples for using the package as a SOAP client.

  • A simple SOAP call to a Perl implemented service using http.
% package require SOAP
1.6
% SOAP::create c2f -uri http://www.soaplite.com/Temperatures \
     -proxy http://services.soaplite.com/temper.cgi \
     -params { "temp" "float" }
::c2f
% c2f -40.0
24.8
  • SOAP over SMTP (sends the request to the proxy url).
% package require SOAP::smtp
1.0
% SOAP::configure -transport mailto -servers smtp.localdomain.org
% SOAP::create POSSale -uri urn:tclsoap::POS \
    -proxy mailto:[email protected] \
    -params { productid string amount float operator string }
::POSSale
% POSSale "0123232-0912" 10.00 "Sally"
%
  • SOAP over FTP
% package require SOAP::ftp
1.0
% SOAP::create POSSale -uri urn:tclsoap:POS \
    -proxy ftp://anonymous:[email protected]/soap/POS \
    -params { productid string amount float operator string }
::POSSale
% POSSale "0123232-0912" 10.00 "Sally"

Cameron Laird brought this example (temperature retrieval by zip code) in [L2 ]:

# Declare required libraries.
package require SOAP
package require Tk

# Create a simple GUI display.  Bind the entry display
#     to "zipcode".
entry .my_entry -textvariable zipcode
label .my_label
pack .my_entry .my_label

# describes this service.
set URI urn:xmethods-Temperature
set URL http://services.xmethods.net/soap/servlet/rpcrouter

# Bind a local command to the remote service.
SOAP::create getTemp -uri $URI -proxy $URL -params {zipcode string}

# Invoke the SOAP-mediated local command when the user
#    pushes the "Return" key.  Display the return value.
bind <Return> .my_entry  {
    .my_label configure -text \
        "The temperature at $zipcode is [getTemp $zipcode]."
}     

snichols Note: The above SOAP method is in place, but always returns -999 no matter what zip code is passed in.


Michael Jacobson adds this example used in Play Chess With a WebService. Using the interface specification from http://www.danmarinescu.com/webservices.htm and shown below...

  • Position: char[64], starting from upper left, ending to upper right, empty squares are spaces, universal chess notation (whites are capitals), whites are always down. You will have to send the position every single time you access the interface. For example, the initial position will be: "rnbqkbnrpppppppp {insert 4*8=32 spaces in here!} PPPPPPPPRNBQKBNR" (the spaces for empty squares are mandatory).
  • WhiteMovesNext: bool, defaulted to true.
  • SearchDepth: int, defaulted to 3, this would be the search depth of the Chess algorithm (please do not abuse the server, do not send values bigger than 5). The default value (3) stands for about 2100 elo... The thinking time growth is an exponential of the search depth

We can come up with a simple example of the white opening move in a chess game.

package require SOAP
set uri "urn:BorlandChess-IBorlandChess#XML_GetNextMove"
set proxy "http://www.danmarinescu.com/WebServices/ChessCGIServer.exe/soap/IBorlandChess"
SOAP::create XML_GetNextMove \
     -uri $uri -proxy $proxy \
     -params {"Position" "string" "WhiteMovesNext" "boolean" "SearchDepth" "integer"}
XML_GetNextMove "rnbqkbnrpppppppp[string repeat { } 32]PPPPPPPPRNBQKBNR" true 3  
# returns "e2e4 OK"

Michael Jacobson also adds and example to view the current price of a auction item on ebay with tclSOAP.

package require SOAP
set uri "urn:xmethods-EbayWatcher"
set proxy "http://services.xmethods.net:80/soap/servlet/rpcrouter"
SOAP::create getCurrentPrice -uri $uri -proxy $proxy \
 -params {"auction_id" "string"}
#getCurrentPrice 3218123285  ;# use a valid auction item number from ebay

Discussion

A fairly large number of people have trouble finding a suitable TclDOM package to use with the SOAP package. I have therefore placed the versions of TclDOM 2.0 that I use for development at http://tclsoap.sourceforge.net/dom-packages.html You can still get the canonical TclDOM code from http://sourceforge.net/projects/tclxml PT

An alternative is to get a binary distribution like ActiveTcl, which contains all the necessary packages.


In the course of answering a question [L3 ] about how to transmit content which is itself an XML document, TclSOAP author Pat Thoyts makes these points: "You should be fine passing in XML data. It will need to be quoted properly as per XML specs ie: < as &lt; etc.

DAF notes that some servers are not expecting this xml to be escaped, and throw their toys out of the cot when you send escaped xml. I've made a patch which makes the behabiour of TCLSoap more like that of Perl's SOAP::Lite in this regard. It basically introduces a parameter type xml which is treated differently from a standard string -- a dom document object is created, and the root node inserted under the named parameter in the SOAP envelope's body, for example:

# override the SOAP::insert_value command:
proc ::SOAP::insert_value {node value} {

    set type     [rpctype $value]
    set subtype  [rpcsubtype $value]
    set attrs    [rpcattributes $value]
    set headers  [rpcheaders $value]
    set value    [rpcvalue $value]
    set typeinfo [typedef -info $type]
    set typexmlns [typedef -namespace $type]

    # Handle any header elements
    if {$headers != {}} {
        insert_headers $node $headers
    }
    
    # If the rpcvar namespace is a URI then assign it a tag and ensure we
    # have our colon only when required.
    if {$typexmlns != {} && [regexp : $typexmlns]} {
        dom::element setAttribute $node "xmlns:t" $typexmlns
        set typexmlns t
    }
    if {$typexmlns != {}} { append typexmlns : }

    # If there are any attributes assigned, apply them.
    if {$attrs != {}} {
        foreach {aname avalue} $attrs {
            dom::element setAttribute $node $aname $avalue
        }
    }

    if {[string match {*()} $typeinfo] || [string match {*()} $type] 
        || [string match array $type]} {
        # array type: arrays are indicated by one or more () suffixes or
        # the word 'array' (depreciated)

        if {[string length $typeinfo] == 0} {
            set dimensions [regexp -all -- {\(\)} $type]
            set itemtype [string trimright $type ()]
            if {$itemtype == "array"} {
                set itemtype ur-type
                set dimensions 1
            }
        } else {
            set dimensions [regexp -all -- {\(\)} $typeinfo]
            set itemtype [string trimright $typeinfo ()]
        }
        
        # Look up the typedef info of the item type
        set itemxmlns [typedef -namespace $itemtype]
        if {$itemxmlns != {} && [regexp : $itemxmlns]} {
            dom::element setAttribute $node "xmlns:i" $itemxmlns
            set itemxmlns i
        }
        
        # Currently we do not support non-0 offsets into the array.
        # This is because I don;t know how I should present this to the
        # user. It's got to be a dynamic attribute on the value.
        dom::element setAttribute $node \
                "xmlns:SOAP-ENC" "http://schemas.xmlsoap.org/soap/encoding/"
        dom::element setAttribute $node "xsi:type" "SOAP-ENC:Array"
        dom::element setAttribute $node "SOAP-ENC:offset" "\[0\]"

        # we need to break a multi-dim array into r0c0,r0c1,r1c0,r1c1
        # so list0 followed by list1 etc.
        # FIX ME
        set arrayType "$itemxmlns:$itemtype"
        #for {set cn 0} {$cn < $dimensions} {incr cn}
        append arrayType "\[[llength $value]\]"
        dom::element setAttribute $node "SOAP-ENC:arrayType" $arrayType

        foreach elt $value {
            set d_elt [dom::document createElement $node "item"]
            if {[string match "ur-type" $itemtype]} {
                insert_value $d_elt $elt
            } else {
                insert_value $d_elt [rpcvar $itemtype $elt]
            }
        }
    } elseif {[llength $typeinfo] > 1} {
        # a typedef'd struct.
        if {$typexmlns != {}} {
            dom::element setAttribute $node "xsi:type" "${typexmlns}${type}"
        }
        array set ti $typeinfo
        # Bounds checking - <[email protected]>
        if {[llength $typeinfo] != [llength $value]} {
            return -code error "wrong # args:\
                type $type contains \"$typeinfo\""
        }
        foreach {eltname eltvalue} $value {
            set d_elt [dom::document createElement $node $eltname]
            if {![info exists ti($eltname)]} {
                return -code error "invalid member name:\
                    \"$eltname\" is not a member of the $type type."
            }
            insert_value $d_elt [rpcvar $ti($eltname) $eltvalue]
        }
    } elseif {$type == "struct"} {
        # an unspecified struct
        foreach {eltname eltvalue} $value {
            set d_elt [dom::document createElement $node $eltname]
            insert_value $d_elt $eltvalue
        }
    } elseif {$type == "xml"} {
        # straight xml that needs to be inline'd
        set subdoc [dom::parse $value]
        set newnode [dom::node appendChild $node\
                [dom::document cget $subdoc -documentElement]]
        dom::element setAttribute $node "xsi:type" "xsd:string"
    } else {
        # simple type or typedef'd enumeration
        if {$typexmlns != {}} {
            dom::element setAttribute $node "xsi:type" "${typexmlns}${type}"
        }
        dom::document createTextNode $node $value
    }
}

# create your SOAP command:
SOAP::create foo -proxy http://myfoo.bar.com -uri "urn:foo.bar" -action "urn:foo.bar" -params {name string zipcode integer userdetails xml}
# run that command:
foo "Mr Foo" "4001" "<userdetails><favourites color=\"blue\" food=\"chocolate\"/><profession>1337 h4x0r</profession></userdetails>"

I hope that my patch will be taken into consideration for the official TCLSoap package -- apart from this patch, the package did exactly what I needed, and was significantly faster than perl's SOAP::Lite.


Here is a brief example using TclSOAP and tclhttpd as the server. I run these commands under TkCon...

source tclhttpd3.3/bin/httpd.tcl ;# start the web server
package require SOAP
package require SOAP::Domain
SOAP::Domain::register -prefix /soap -namespace zsplat::Test

# Define a server procedure to return some XML
proc xml {} {
    return {<?xml version="1.0" ?><Memo/>}
}

SOAP::create zxml -proxy http://localhost:8015/soap/xml -uri zsplat-Test
zxml

returns <?xml version="1.0><Memo/>

and SOAP::dump zxml returns

<?xml version='1.0'?>
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/1999/XMLSchema"><SOAP-ENV:Body><ns:return xmlns:ns="zsplat-Test" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><return xsi:type="xsd:string">&lt;?xml version=&quot;1.0&quot;?&gt;&lt;Memo/&gt;</return></ns:return></SOAP-ENV:Body></SOAP-ENV:Envelope>

If you have problems you could always pack up the XML as base64 encoded data and send that instead."

  • The above code will only work for versions prior to 1.6. For newer versions you would create a tcl script file with the method implementation and place it into a directory. Then start tclhttpd, source tclserver.tcl, set the SOAP::CGI::soapdir variable and let rip. Also, newer code needs to be 'exported'. This is so that callers cannot execute arbitrary tcl commands like exec. PT

In a comp.lang.tcl posting, Pat explains that TclSOAP 1.6 includes new capabilities for "structure" transmission. He offers

package require rpcvar
namespace import rpcvar::typedef

typedef {
   intValue    int
   floatValue  float
   stringValue string
} SoapStruct

SOAP::create someMethod -params {aStruct SoapStruct}
someMethod {intValue 2 floatValue 3.5 stringValue "Hello World"}

as an example usage.


There was a comment regarding problems with TclSOAP and why someone choose the Java SOAP instead recently on comp.lang.tcl .

PT: So stick in a link then! The reason they made this decision was at my advice. In September 2001 the package was still pretty new and only operated with Tcl-only versions of TclDOM. The relative sluggishness of Tcl for processing DOM trees combined with the CGI process startup for the naive implementation of SOAP services under CGI meant that they were better off using some other system for the server side of their application. You should node that they continued to use TclSOAP for the client side.

Since that time - fixes to the TclDOM C module (dom::c) have made it possible to use a significantly faster DOM processor. Changes to the method used to host the SOAP server side should also improve performance. For instance, using mod_dtcl with Apache or using tclhttpd would mean that there will pretty much no startup delay.

The TclSOAP site at http://tclsoap.sourceforge.net/ hosts some SOAP services - namely implementations of the SOAP Interoperability Lab Test Suite for round 1 and 2. You can test the relative performance of these services against other similar implementations. The server is Apache and using the C TclDOM but nothing has been done (yet) about improving the CGI handling.

If your services are going to be performance intensive I would still recommend using another SOAP toolkit (perl or easysoap++ maybe). However, if you want to generate some protptypes fast and/or flexibly the Tcl is always there.

CL adds: I'll emphasize again that it's ENTIRELY OK to use a different SOAP toolkit for servers than for clients. Tcl/Tk is way cool on the client side--download ActiveTcl, and you're completely ready to build nice clients. Tcl is less competitive for SOAP servers, in my opinion. Also, while I'm sharing my sentiments: Pat's done great work.


More on server-side SOAP work: even as of version 1.6.1, Pat agrees, "The client code is generally very easy to use but the server side stuff is much less so."

Specifically, "To run TclSOAP with tclhttpd there is a tclserver.tcl file in samples/ which can use the SOAP::CGI style setup and code within tclhttpd. It works but the server code could generally use a bit of a redesign to simplify things."

In Pat's summary:

source tclhttpd/bin/httpd.tcl
source tclsoap/samples/tclserver.tcl

are the best current documentation for server-side possibilities using tclhttpd.

PT: I'd suggest reading http://tclsoap.sf.net/SOAP-CGI.html for information on providing a SOAP service using TclSOAP. The example above is the current howto for providing SOAP services from within tclhttpd. This is not as pleasant an experience as it might be due to the reworkings I performed to get it working with CGI under Apache. The SOAP::Domain package was a nicer tclhttpd solution. In the near future I'm going to try and construct a better tclhttpd solution.


SC 2003-09-24: I was just looking at how to add an XMLRPC interface to giggle so that I could implement the blogger API for posting new messages. Currently giggle is a single library called via a simple main script, it handles all uris below it (eg. ../giggle.tcl/foo/bar) through simple CGI and packs nicely into a starkit. It seems from the SOAP::CGI docs that to add an XMLRPC interface I need to supply multiple files to implement the interface (of course these could be inside the starkit). What would be neater would be to be able to get the posted XML and pass it to a proc for interpretation having registered some already loaded procs as being available via XMLRPC. It looks like ::SOAP::CGI::xmlrpc_invocation almost does this -- except it does the sourcing of tcl files too. Would it be easy to provide this kind of entry point into the server side of tclSOAP?

PT 2003-09-25:

The reason we have the re-sourcing all the time is to support CGI based services, where the whole lot is being loaded into a new interpreter for each call. What you are after is the tclhttpd support which is in the SOAP::Domain subpackage. At this time support for XMLRPC is missing, but either I'll get around to it or you could use the code in the SOAP::Domain package to fix the XMLRPC::Domain code.

The tclhttpd-style support is a much better solution than CGI-based -- but it just so happens that I have to use CGI for my services, hence we support CGI better than tclhttpd.


BFF 2010-02-05 10:08:47:

What's URI? I'm trying to install TclSOAP and am getting this error:

% package require SOAP
can't find package uri
% package require uri
can't find package uri

I thought uri was included as part of TclLIB which I have installed:

% package require logger 0.3
0.9

Can anyone help? I'm running ActiveTCL 8.5.7.0 on a Windows XP box.


AK2010-02-05 11:38:59:

The ActiveTcl 8.5 series has a reduced set of packages installed by default. The package logger is in that set, uri is not. Any and all packages not installed can be installed by using the teacup repository client. Either

teacup install package-name

to install a specific package and its dependencies, or

teacup update

to pull everything which is not installed yet, or has a higher version number (= newer) in the repository.