You are looking at historical revision 15227 of this page. It may differ significantly from its current revision.

xml-rpc

Description

A library for XML-RPC client/servers.

Author

Peter Bex (inspired by and using code from an earlier egg by Felix Winkelmann)

Requirements

Documentation

This implementation of XML-RPC is extended to allow returning multiple values. Errors during the execution of a server-method are propagated to the client as "fault" responses.

Client

Usage:

 (use xml-rpc-client)
[procedure] (xml-rpc-server uri)

Returns a procedure that, when called with the name of a remote XML-RPC method, will return a procedure that passes its arguments to the XML-RPC server which is given in uri (which can be an URI object or a string representing an URI).

To determine how XML-RPC types are mapped to Scheme types and vice-versa, see below. It's important that you read this, because there is some ambiguity in how lists are mapped (either to arrays or to structs).

For lower-level access to the client (implementing custom handlers, for example), you can use the following procedures:

[procedure] (xml-rpc-methodcall method-name args)

Constructs an SXML representation of a method call to the procedure method-name with an arguments list of args.

[procedure] (xml-rpc-response->values response-sxml)

This procedure accepts as response-sxml the SXML representation of a server's response, and either returns the values returned by the procedure call encoded in the response, or throws an exception of type exn xml-rpc in case the response contains invalid data.

Server

The server is still under construction. The building blocks for creating your own server are in place already:

Usage:

 (require-extension xml-rpc-server)
[procedure] (xml-rpc-call->xml-rpc-response call-sxml procedures)

This procedure converts an XML-RPC procedure call described by call-sxml into an SXML representation of the result. The procedure is looked up in procedures, invoked, and its return values are converted into the appropriate SXML structure describing a methodResponse. If an error occurs inside the procedure, the procedure does not exist, or the XML is invalid, a methodResponse encoding the fault is constructed instead.

procedures is an alist of procedure name (symbols) to procedure (lambda) mappings. The procedures are called with exactly the arguments that are sent by the client, encoded in the call (call-sxml). They will be converted to regular Scheme values before the procedure is invoked.

To determine how XML-RPC types are mapped to Scheme types and vice-versa, see below. It's important that you read this, because there is some ambiguity in how lists are mapped (either to arrays or to structs).

Examples:

(use xml-rpc-server)

(xml-rpc-call->xml-rpc-response
  `(*TOP*
     (*PI* xml "version=\"1.0\"")
     (methodCall
     (methodName "scheme.makeList")
       (params
         (param (value (int "1")))
         (param (value (int "2")))
         (param (value (int "3"))))))
  `((scheme.makeList . ,list)))
=>

(methodResponse
  (params
    (param (value
             (array
	       (data
	         (value (i4 "1"))
                 (value (i4 "2"))
                 (value (i4 "3"))))))))
[procedure] (call-xml-rpc-proc call-sxml procedures)

This procedure accepts as call-sxml the SXML representation of a procedure call from a client and calls it, returning its values.

This is exactly like xml-rpc-call->xml-rpc-response, except it does not construct an SXML result tree. Instead, the return values are those returned by the procedure being called. In case the procedure could not be found or if the call contains an invalid XML structure, an exception of type (exn xml-rpc) is thrown. The xml-rpc part of the condition contains a code property which contains the fault code. This is 1 in case the procedure could not be found and 2 in case the XML is bad.

Examples:

(use xml-rpc-server)

(call-xml-rpc-proc
  `(*TOP*
     (*PI* xml "version=\"1.0\"")
     (methodCall
      (methodName "Math.add")
      (params
       (param (value (int "1")))
       (param (value (int "2")))
       (param (value (int "3"))))))
   `((Math.add . ,+)))
=>

6

Low-level

Sometimes you want complete control over how Scheme values are mapped to XML-RPC values and vice versa. For that, use this module.

Usage:

 (use xml-rpc-lolevel)
Scheme to XML-RPC
[parameter] (xml-rpc-unparsers [alist])

This parameter controls how Scheme values are encoded into XML-RPC values. The keys of this alist are predicate procedures, the values are conversion procedures. If the predicate procedure returns true for its argument, it's a datatype that will be converted to SXML by the matching conversion procedure.

Defaults to:

`((,vector? . ,vector->xml-rpc-array)
  (,(conjoin number? exact?) . ,number->xml-rpc-int)
  (,number? . ,number->xml-rpc-double)
  (,boolean? . ,boolean->xml-rpc-boolean)
  (,string? . ,->xml-rpc-string)
  (,symbol? . ,->xml-rpc-string)
  (,u8vector? . ,u8vector->xml-rpc-base64)
  (,blob? . ,blob->xml-rpc-base64)
  (,hash-table? . ,hash-table->xml-rpc-struct)
  ;; see below for an explantation of this predicate
  (,nonempty-symbol-keyed-alist? . ,alist->xml-rpc-struct)
  (,list? . ,list->xml-rpc-array))

Order matters in this alist; the converter corresponding to the first predicate returning a true value is used.

The SXML returned by these conversion procedures is the element inside the value element.

Examples:

(use xml-rpc-lolevel)

(number->xml-rpc-int 1)
=>

(i4 "1")
[procedure] (value->xml-rpc-fragment value)

This procedure converts any Scheme value to SXML for its XML-RPC representation. It looks up the conversion procedure in the xml-rpc-unparsers parameter.

[procedure] (nonempty-symbol-keyed-alist? obj)

Returns #t when obj is a nonempty list of pairs, each of which has a symbol as car.

The idea behind this predicates is that it helps to do "The Right Thing" when you call an XML-RPC procedure. You can pass in regular lists or alists, and it will try to make the right decision whether to convert your lists to structs or arrays.

The predicate returns true for nonempty lists only because it's much more likely that you will have empty regular lists than empty alists. However, it's important to be aware of this because you might end up with an empty alist. For absolute safety, remove this predicate from the parameter and use only hash-tables.

These procedures pretty much do the obvious thing: they encode a Scheme object of the given type to an SXML representation for use in the XML-RPC request. Again, the return values look like (i4 "1") and (string "foo"), not like (value (string "foo")). Inside arrays and structs, the value is automatically wrapped around the right values.

[procedure] (->xml-rpc-string obj)

This procedure converts the obj to string with ->string and then encodes it in SXML as an XML-RPC string value. This is useful for passing symbols, regular strings or numbers to procedures expecting string representation.

[procedure] (vector->xml-rpc-iso8601 time-vector)

This procedure encodes a "time vector" (10-element vector, as returned by eg seconds->local-time) to an iso8601 string representing the same date. Currently this procedure is not in the parameter list by default, because it's impossible to differentiate between a regular vector that just happens to be 10 elements long and a "time-vector". The same problem exists for integers and the "seconds since the epoch" representation of time.

Using srfi-19 is a solution to this problem, as it provides a distinct datatype for date/time objects. But you would have to make your own conversion routines in this case.

XML-RPC to Scheme
[parameter] (xml-rpc-parsers [alist])

This parameter controls how XML-RPC values are decoded back into Scheme values. The keys of this alist are symbols, the values are conversion procedures. If the name of a predicate procedure returns true for its argument, it's a datatype that will be converted to SXML by the matching conversion procedure.

Defaults to:

`((i4 . ,xml-rpc-int->number)
  (int . ,xml-rpc-int->number)
  (double . ,xml-rpc-double->number)
  (boolean . ,xml-rpc-boolean->number)
  (string . ,xml-rpc-string->string)
  (base64 . ,xml-rpc-base64->u8vector)
  (dateTime.iso8601 . ,xml-rpc-datetime->vector)
  (array . ,xml-rpc-array->vector)
  (struct . ,xml-rpc-struct->hash-table))

The SXML arguments to these conversion procedures is the element inside the value element. In other words, the element name (or car)of its SXML argument is equal to its key in this alist.

[procedure] (xml-rpc-fragment->value sxml-fragment)

This procedure converts an SXML representation of an XML-RPC value to its Scheme representation. It looks up the conversion procedure in the xml-rpc-parsers parameter.

Examples:
(use xml-rpc-lolevel)

(xml-rpc-fragment->value '(i4 "1"))
=>

1

Convert the given sxml-fragment to the corresponding Scheme value.

Examples

Fetch time from xml-rpc.org:

(require-extension xml-rpc-client)

(define time-server
  (xml-rpc-server "http://xml-rpc.org/RPC2") )
  
(define get-current-time
  (time-server "currentTime.getCurrentTime") )
  
(print (time->string (get-current-time)))

A simple "hello" server: (this does not currently function and will be changed)

(require-extension xml-rpc-server)
  
(define-remote-method (hello var)
  (sprintf "Hello, ~A!" var) )
  
((start-server 4242))

You can access it using this client:

(require-extension xml-rpc-client)
 
(define srv (xml-rpc-server "http://localhost:4242/RPC2"))
(define hello (srv "hello"))
  
(print "-> " (hello "you"))

Then run it as follows:

 % csi -script hello.scm &
 % csi -script client.scm
 
 -> Hello, you!

Changelog

License

 Copyright (c) 2009, Peter Bex
 Parts Copyright (c) Felix Winkelmann
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
 met:
 
   Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
 
   Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
 
   Neither the name of the author nor the names of its contributors may
   be used to endorse or promote products derived from this software
   without specific prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 DAMAGE.