[plt-scheme] using continuations for an async protocol
Hello,
I am writing a jabber client library and have a few questions.
The jabber protocol is asynchronous, so there is no guarantee that an
incoming message is a response to the last sent message. You may tag an
outgoing message with an ID that is copied in the future response
message.
One small example is a 'login' procedure: (note: jabber is xml-based)
;; login: JabberClient string string -> void
(define (login client username password)
(let ([res (iq-set client
`(query ([xmlns ,ns:iq-auth])
(username ,username)
(password ,password)
(resource "DrScheme")))])
(if (string=? "error" (a-lookup 'type res))
(printf "Login error.")
(printf "Login successful."))))
Where ns:iq-auth is an xml namespace string, a-lookup finds an XML
attribute value in an element, res is an x-expression element, and
iq-set is:
;; iq-set: JabberClient x-expression -> sxp
(define (iq-set client query-xexpr)
;; generate an id, but not a gensym.. so wrap it in a string
(let ([id (format "~a" (gensym 'iqset))])
(let/cc k
(hash-table-put! *conts* (string->symbol id) k)
;; send the tagged query message to the jabber server
(jwrite client `(iq ([type "set"]
[id ,id])
,query-xexpr))
;; suspend?
(update client))))
Where jwrite sends an x-expression to a jabber client, and update is:
(define (update client)
(let* ([xexpr (jread client)] ;; read incoming message
[id (a-lookup 'id xexpr)]) ;; find its id, if it has one
(if id
(let* ([key (string->symbol id)]
[k (hash-table-get *conts* key)]) ; find the stored k
(hash-table-remove! *conts* key) ; clean up
(k xexpr))
(begin (dispatch xexpr) ;; dispatch untagged messages
(update client))))) ;; rinse, repeat
Where jread reads an x-expression from a client and dispatch handles
targetless (id-less) messages like notifications about a contact going
away or coming back online.
Am I going about this [async. protocols] the right way? Using this
implementation, any server notifications that come in between the time I
send my login info and when the auth server responds will be handled
correctly (and won't be mistaken for the response).
I could have done something similar to what the Haskell guys did in
WASH/CGI and passed a lambda to iq-set in the login procedure instead of
using let/cc to get a returned value, but that doesn't feel natural to
me. Is it the right solution though?
Also, would it make more sense to run "update" in a separate thread?
Though I don't think it could apply "k" then since it wouldn't own the
continuation.
asking +inf.0 questions,
Daniel