source: project/wiki/eggref/4/xml-rpc @ 15257

Last change on this file since 15257 was 15257, checked in by sjamaan, 10 years ago

Explicitly list intarweb, since we use that directly in the egg

File size: 15.5 KB
Line 
1[[tags: egg]]
2
3== xml-rpc
4
5[[toc:]]
6
7=== Description
8
9A library for [[http://www.xmlrpc.com|XML-RPC]] client/servers.
10
11=== Author
12
13[[/users/peter-bex|Peter Bex]] (inspired by and
14using code from [[/eggref/3/xml-rpc|an earlier egg]] by
15[[/users/felix-winkelmann|Felix Winkelmann]])
16
17=== Requirements
18
19* [[http-client]]
20* [[intarweb]]
21* [[ssax]]
22* [[sxpath]]
23* [[base64]]
24
25=== Documentation
26
27This implementation of XML-RPC is extended to allow returning multiple
28values. Errors during the execution of a server-method are propagated
29to the client as "fault" responses.
30
31==== Client
32
33Usage:
34  (use xml-rpc-client)
35
36<procedure>(xml-rpc-server uri)</procedure>
37
38Returns a procedure that, when called with the name of a remote
39XML-RPC method, will return a procedure that passes its arguments
40to the XML-RPC server which is given in {{uri}} (which can be an URI
41object or a string representing an URI).
42
43To determine how XML-RPC types are mapped to Scheme types and
44vice-versa, see [[#Low-level|below]].  It's important that you read
45this, because there is some ambiguity in how lists are mapped (either
46to arrays or to structs).
47
48Here is an example that fetches the current time of day from xml-rpc.org:
49
50<enscript highlight=scheme>
51(require-extension xml-rpc-client)
52
53(define time-server
54  (xml-rpc-server "http://xml-rpc.org/RPC2") )
55 
56(define get-current-time
57  (time-server "currentTime.getCurrentTime") )
58 
59(print (time->string (get-current-time)))
60</enscript>
61
62
63For lower-level access to the client (implementing custom handlers,
64for example), you can use the following procedures:
65
66<procedure>(xml-rpc-methodcall method-name args)</procedure>
67
68Constructs an SXML representation of a method call to the procedure
69{{method-name}} with an arguments list of {{args}}.
70
71<examples>
72<example>
73<init>
74(use xml-rpc-client)
75</init>
76<expr>
77(xml-rpc-methodcall 'scheme.makeList '(1 2 3 "testing"))
78</expr>
79<result>
80(methodCall
81  (methodName "scheme.makeList")
82  (params
83    (param (value (i4 "1")))
84    (param (value (i4 "2")))
85    (param (value (i4 "3")))
86    (param (value (string "testing")))))
87</result>
88</example>
89</examples>
90
91<procedure>(xml-rpc-response->values response-sxml)</procedure>
92
93This procedure accepts as {{response-sxml}} the SXML representation of
94a server's response, and either returns the values returned by the
95procedure call encoded in the response, or throws an exception of type
96{{exn xml-rpc}} in case the response contains invalid data.
97
98==== Server
99
100Usage:
101  (require-extension xml-rpc-server)
102
103<procedure>(make-xml-rpc-request-handler procedures)</procedure>
104
105This creates a procedure which accepts two arguments; an [[intarweb]]
106request object and a response object.  It will read an XML-RPC request
107from the request-port and respond to the request with the response
108object, writing to its port.
109
110The procedure that is requested to be called is looked up in the
111{{procedures}} argument, which is an alist of procedure name (symbols)
112to procedure (lambda) mappings. The procedures are called with exactly
113the arguments that are sent by the client, encoded in the call
114({{call-sxml}}). They will be converted to regular Scheme values
115before the procedure is invoked.
116
117To determine how XML-RPC types are mapped to Scheme types and
118vice-versa, see [[#Low-level|below]].  It's important that you read
119this, because there is some ambiguity in how lists are mapped (either
120to arrays or to structs).
121
122<procedure>(start-simple-xml-rpc-server procedures [port])</procedure>
123
124Create a standalone XML-RPC server on {{port}} (defaults to 8080),
125which accepts an XML-RPC request on any URL.
126
127You can also use slightly more low-level procedures to implement your own
128server to be exactly like you want it to be:
129
130<procedure>(xml-rpc-call->xml-rpc-response call-sxml procedures)</procedure>
131
132This procedure converts an XML-RPC procedure call described by
133{{call-sxml}} into an SXML representation of the result.  The
134procedure is looked up in {{procedures}}, invoked, and its return
135values are converted into the appropriate SXML structure describing
136a {{methodResponse}}.  If an error occurs inside the procedure,
137the procedure does not exist, or the XML is invalid, a
138{{methodResponse}} encoding the {{fault}} is constructed instead.
139
140<examples>
141<example>
142<init>
143(use xml-rpc-server)
144</init>
145<expr>
146(xml-rpc-call->xml-rpc-response
147  `(*TOP*
148     (*PI* xml "version=\"1.0\"")
149     (methodCall  ; (xml-rpc-methodcall 'scheme.makeList '(1 2 3))
150       (methodName "scheme.makeList")
151         (params
152           (param (value (int "1")))
153           (param (value (int "2")))
154           (param (value (int "3"))))))
155  `((scheme.makeList . ,list)))
156</expr>
157<result>
158(methodResponse
159  (params
160    (param (value
161             (array
162               (data
163                 (value (i4 "1"))
164                 (value (i4 "2"))
165                 (value (i4 "3"))))))))
166</result>
167</example>
168</examples>
169
170<procedure>(call-xml-rpc-proc call-sxml procedures)</procedure>
171
172This procedure accepts as {{call-sxml}} the SXML representation of a
173procedure call from a client and calls it, returning its values.
174
175This is exactly like {{xml-rpc-call->xml-rpc-response}}, except it
176does not construct an SXML result tree. Instead, the return values are
177those returned by the procedure being called.  In case the procedure
178could not be found or if the call contains an invalid XML structure,
179an exception of type {{(exn xml-rpc)}} is thrown.  The {{xml-rpc}}
180part of the condition contains a {{code}} property which contains the
181fault code. This is {{1}} in case the procedure could not be found and
182{{2}} in case the XML is bad.
183
184<examples>
185<example>
186<init>
187(use xml-rpc-server)
188</init>
189<expr>
190(call-xml-rpc-proc
191  `(*TOP*
192     (*PI* xml "version=\"1.0\"")
193     (methodCall
194      (methodName "Math.add")
195      (params
196       (param (value (int "1")))
197       (param (value (int "2")))
198       (param (value (int "3"))))))
199   `((Math.add . ,+)))
200</expr>
201<result>
2026
203</result>
204</example>
205</examples>
206
207==== Low-level
208
209Sometimes you want complete control over how Scheme values are mapped
210to XML-RPC values and vice versa.  For that, use this module.
211
212Usage:
213  (use xml-rpc-lolevel)
214
215===== Scheme to XML-RPC
216
217<parameter>(xml-rpc-unparsers [alist])</parameter>
218
219This parameter controls how Scheme values are encoded into XML-RPC
220values. The keys of this alist are predicate procedures, the values
221are conversion procedures.  If the predicate procedure returns true
222for its argument, it's a datatype that will be converted to SXML by
223the matching conversion procedure.
224
225Defaults to:
226
227<enscript highlight=scheme>
228`((,vector? . ,vector->xml-rpc-array)
229  (,(conjoin number? exact?) . ,number->xml-rpc-int)
230  (,number? . ,number->xml-rpc-double)
231  (,boolean? . ,boolean->xml-rpc-boolean)
232  (,string? . ,->xml-rpc-string)
233  (,symbol? . ,->xml-rpc-string)
234  (,u8vector? . ,u8vector->xml-rpc-base64)
235  (,blob? . ,blob->xml-rpc-base64)
236  (,hash-table? . ,hash-table->xml-rpc-struct)
237  ;; see below for an explantation of this predicate
238  (,nonempty-symbol-keyed-alist? . ,alist->xml-rpc-struct)
239  (,list? . ,list->xml-rpc-array))
240</enscript>
241
242Order matters in this alist; the converter corresponding to the first
243predicate returning a true value is used.
244
245The SXML returned by these conversion procedures is the element
246''inside'' the {{value}} element.
247
248<examples>
249<example>
250<init>
251(use xml-rpc-lolevel)
252</init>
253<expr>
254(number->xml-rpc-int 1)
255</expr>
256<result>
257(i4 "1")
258</result>
259</example>
260</examples>
261
262<procedure>(value->xml-rpc-fragment value)</procedure>
263
264This procedure converts any Scheme value to SXML for its XML-RPC
265representation.  It looks up the conversion procedure in the
266{{xml-rpc-unparsers}} parameter.
267
268<procedure>(nonempty-symbol-keyed-alist? obj)</procedure>
269
270Returns {{#t}} when {{obj}} is a ''nonempty'' list of pairs, each of
271which has a symbol as {{car}}.
272
273The idea behind this predicates is that it helps to do "The Right
274Thing" when you call an XML-RPC procedure. You can pass in regular
275lists or alists, and it will try to make the right decision whether
276to convert your lists to structs or arrays.
277
278The predicate returns true for nonempty lists only because it's much
279more likely that you will have empty regular lists than empty alists.
280However, it's important to be aware of this because you might end up
281with an empty alist. For absolute safety, remove this predicate from
282the parameter and use only hash-tables.
283
284* <procedure>(list->xml-rpc-array list)</procedure>
285* <procedure>(vector->xml-rpc-array vector)</procedure>
286* <procedure>(number->xml-rpc-int number)</procedure>
287* <procedure>(number->xml-rpc-double number)</procedure>
288* <procedure>(boolean->xml-rpc-boolean boolean)</procedure>
289* <procedure>(u8vector->xml-rpc-base64 u8vector)</procedure>
290* <procedure>(blob->xml-rpc-base64 blob)</procedure>
291* <procedure>(alist->xml-rpc-struct alist)</procedure>
292* <procedure>(hash-table->xml-rpc-struct hash-table)</procedure>
293
294These procedures pretty much do the obvious thing: they encode a
295Scheme object of the given type to an SXML representation for use in
296the XML-RPC request.  Again, the return values look like {{(i4 "1")}}
297and {{(string "foo")}}, ''not'' like {{(value (string "foo"))}}.
298Inside arrays and structs, the {{value}} is automatically wrapped
299around the right values.
300
301<procedure>(->xml-rpc-string obj)</procedure>
302
303This procedure converts the {{obj}} to string with {{->string}} and
304then encodes it in SXML as an XML-RPC string value.  This is useful
305for passing symbols, regular strings or numbers to procedures
306expecting string representation.
307
308<procedure>(vector->xml-rpc-iso8601 time-vector)</procedure>
309
310This procedure encodes a "time vector" (10-element vector, as returned
311by eg [[/man/4/Unit posix#seconds-local-time|seconds->local-time]]) to
312an iso8601 string representing the same date.  Currently this
313procedure is not in the parameter list by default, because it's
314impossible to differentiate between a regular vector that just happens
315to be 10 elements long and a "time-vector". The same problem exists
316for integers and the "seconds since the epoch" representation of time.
317
318Using [[srfi-19]] is a solution to this problem, as it provides a
319distinct datatype for date/time objects. But you would have to make
320your own conversion routines in this case.
321
322===== XML-RPC to Scheme
323
324<parameter>(xml-rpc-parsers [alist])</parameter>
325
326This parameter controls how XML-RPC values are decoded back into
327Scheme values. The keys of this alist are symbols, the values are
328conversion procedures.  If the name of a predicate procedure returns true for
329its argument, it's a datatype that will be converted to SXML by the
330matching conversion procedure.
331
332Defaults to:
333
334<enscript highlight=scheme>
335`((i4 . ,xml-rpc-int->number)
336  (int . ,xml-rpc-int->number)
337  (double . ,xml-rpc-double->number)
338  (boolean . ,xml-rpc-boolean->number)
339  (string . ,xml-rpc-string->string)
340  (base64 . ,xml-rpc-base64->u8vector)
341  (dateTime.iso8601 . ,xml-rpc-datetime->vector)
342  (array . ,xml-rpc-array->vector)
343  (struct . ,xml-rpc-struct->hash-table))
344</enscript>
345
346The SXML arguments to these conversion procedures is the element
347''inside'' the {{value}} element. In other words, the element name
348(or {{car}})of its SXML argument is equal to its key in this alist.
349
350<procedure>(xml-rpc-fragment->value sxml-fragment)</procedure>
351
352This procedure converts an SXML representation of an XML-RPC value to
353its Scheme representation.  It looks up the conversion procedure in
354the {{xml-rpc-parsers}} parameter.
355
356<examples>
357<example>
358<init>(use xml-rpc-lolevel)</init>
359<expr>
360(xml-rpc-fragment->value '(i4 "1"))
361</expr>
362<result>
3631
364</result>
365</example>
366</examples>
367
368* <procedure>(xml-rpc-int->number sxml-fragment)</procedure>
369* <procedure>(xml-rpc-double->number sxml-fragment)</procedure>
370* <procedure>(xml-rpc-boolean->number sxml-fragment)</procedure>
371* <procedure>(xml-rpc-string->string sxml-fragment)</procedure>
372* <procedure>(xml-rpc-array->vector sxml-fragment)</procedure>
373* <procedure>(xml-rpc-array->list sxml-fragment)</procedure>
374* <procedure>(xml-rpc-struct->alist sxml-fragment)</procedure>
375* <procedure>(xml-rpc-struct->hash-table sxml-fragment)</procedure>
376* <procedure>(xml-rpc-base64->string sxml-fragment)</procedure>
377* <procedure>(xml-rpc-base64->u8vector sxml-fragment)</procedure>
378* <procedure>(xml-rpc-base64->blob sxml-fragment)</procedure>
379* <procedure>(xml-rpc-datetime->vector sxml-fragment)</procedure>
380
381Convert the given {{sxml-fragment}} to the corresponding Scheme value.
382
383=== Examples
384
385A simple "hello" server:
386
387<enscript highlight=scheme>
388(require-extension xml-rpc-server)
389
390(define (say-hello var)
391  (sprintf "Hello, ~A!" var) )
392
393((start-simple-xml-rpc-server `((hello . ,say-hello)) 4242))
394</enscript>
395
396You can access it using this client:
397
398<enscript highlight=scheme>
399(require-extension xml-rpc-client)
400
401(define srv (xml-rpc-server "http://localhost:4242/RPC2"))
402(define hello (srv "hello"))
403
404(print "-> " (hello "you"))
405</enscript>
406
407Then run it as follows:
408
409  % csi -script hello.scm &
410  % csi -script client.scm
411 
412  -> Hello, you!
413
414The same server, running as a Spiffy vhost request handler:
415
416<enscript highlight=scheme>
417(use spiffy xml-rpc)
418
419(define (say-hello var)
420  (sprintf "Hello, ~A!" var) )
421
422(vhost-map
423  `(("test1" . ,(let ((handler (make-xml-rpc-request-handler
424                                 `((hello . ,say-hello)))))
425                  (lambda _
426                     (handler (current-request)
427                              (current-response)))))))
428
429(start-server)
430</enscript>
431
432When requesting any resource under the host {{test1}}, this will
433trigger the XML-RPC handler.
434
435<enscript highlight=scheme>
436(require-extension xml-rpc-client)
437
438(define srv1 (xml-rpc-server "http://test1:8080/RPC2"))
439(define hello1 (srv "hello"))
440
441(define srv2 (xml-rpc-server "http://localhost:8080/RPC2"))
442(define hello2 (srv "hello"))
443
444
445;; Prints:
446;; -> Hello, you!
447(print "-> " (hello1 "you"))
448
449;; Throws an (exn http client-error), with 404 not found:
450(print "-> " (hello2 "you"))
451</enscript>
452
453If you wish more control over exactly under which URIs the XML
454resource is available, you could have a look at [[uri-dispatch]], or
455roll your own URI path checker.
456
457=== Changelog
458
459* 2.0 Reimplementation in Chicken 4, based on [[http-client]]
460
461=== License
462
463  Copyright (c) 2009, Peter Bex
464  Parts Copyright (c) 2003-2006, Felix Winkelmann
465  All rights reserved.
466 
467  Redistribution and use in source and binary forms, with or without
468  modification, are permitted provided that the following conditions are
469  met:
470 
471    Redistributions of source code must retain the above copyright
472    notice, this list of conditions and the following disclaimer.
473 
474    Redistributions in binary form must reproduce the above copyright
475    notice, this list of conditions and the following disclaimer in the
476    documentation and/or other materials provided with the distribution.
477 
478    Neither the name of the author nor the names of its contributors may
479    be used to endorse or promote products derived from this software
480    without specific prior written permission.
481 
482  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
483  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
484  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
485  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
486  HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
487  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
488  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
489  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
490  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
491  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
492  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
493  DAMAGE.
Note: See TracBrowser for help on using the repository browser.