source: project/wiki/eggref/5/xml-rpc @ 40022

Last change on this file since 40022 was 40022, checked in by Christopher Brannon, 2 months ago

eggref/5/xml-rpc: update changelog.

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