#lang racket
(require (planet jim/webit:1:4/xml)
(planet lshift/xxexpr:1/xxexpr)
net/url
(prefix-in ssax: (planet lizorkin/ssax:2:0/ssax))
"base.rkt"
"serialise.rkt")
(provide encode-xml-rpc-call
write-xml-rpc-call
make-xml-rpc-call
read-xml-rpc-response
decode-xml-rpc-response
decode-xml-rpc-call
(struct-out rpc-call))
(define (http-200? headers)
(if (regexp-match #rx"^HTTP/[0-9]*\\.[0-9]* 200" headers)
#t
#f))
(define (http-404? headers)
(if (regexp-match #rx"^HTTP/[0-9]*\\.[0-9]* 404" headers)
#t
#f))
(define (encode-xml-rpc-call method-name . args)
`(methodCall
(methodName ,method-name)
(params
,@(map (lambda (val)
`(param ,(serialise val)))
args))))
(define (write-xml-rpc-call call op)
(parameterize
((xml-double-quotes-mode #t))
(let ([result
(pretty-print-xxexpr (list '(*pi* xml (version "1.0"))
call) op)])
result)))
(define (make-xml-rpc-call url call)
(let ((op (open-output-bytes)))
(write-xml-rpc-call call op)
(post-impure-port url
(get-output-bytes op)
'("Content-Type: text/xml"
"User-Agent: Racket"))))
(define (read-xml-rpc-response ip)
(let ((headers (purify-port ip)))
(cond
[(http-404? headers)
(raise-exn:xml-rpc "Server responded with a 404: File not found")]
[(not (http-200? headers))
(raise-exn:xml-rpc
(format "Server did not respond with an HTTP 200~nHeaders:~n~a~n"
headers))])
(let ([response (ssax:ssax:xml->sxml ip '())])
(close-input-port ip)
response) ))
(define (decode-xml-rpc-response ip)
(let ((resp (read-xml-rpc-response ip)))
(xml-match (xml-document-content resp)
[(methodResponse (params (param ,value)))
(deserialise value)]
[(methodResponse (fault ,value))
(let ((h (deserialise value)))
(raise
(exn:xml-rpc:fault
(string->immutable-string
(hash-ref h 'faultString))
(current-continuation-marks)
(hash-ref h 'faultCode))))]
[,else
(raise-exn:xml-rpc
(format "Received invalid XMLRPC response ~a\n" else))])))
(define (extract-parameter-values param*)
(map (lambda (p)
(xml-match p
[(param ,value) (deserialise value)]
[,else
(raise-exn:xml-rpc
(format "Bad parameter in methodCall: ~a~n" p))]))
param*))
(define (read-xml-rpc-call str)
(let* ([call-ip (open-input-string str)]
[result (ssax:ssax:xml->sxml call-ip '())])
(close-input-port call-ip)
result))
(define-struct rpc-call (name args))
(define (decode-xml-rpc-call str)
(let ([docu (read-xml-rpc-call str)])
(xml-match (xml-document-content docu)
[(methodCall (methodName ,name) (params ,param* ...))
(let ([value* (extract-parameter-values param*)])
(make-rpc-call (string->symbol name) value*))]
[,else
(raise-exn:xml-rpc
(format "Cannot parse methodCall: ~a~n" else))])))