[racket] Implementing long polling ajax

From: Jay McCarthy (jay.mccarthy at gmail.com)
Date: Thu Dec 5 10:02:54 EST 2013

Hi Antoine,

As Greg mentions, long-polling just means "wait to send the response",
so it's very easy to just wait on something before sending the
response. Here's a very simple implementation:

#lang racket/base
(require web-server/http
         net/url
         racket/runtime-path)

;; xxx take arguments from response/output for metadata
(define (response/when evt make-response)
  (response/output
   (λ (op)
     (sync evt)
     (define r (make-response))
     ((response-output r) op))))

(define-runtime-path base ".")
(define (start req)
  (define file
    (map path/param-path (url-path (request-uri req))))

  (response/when
   (filesystem-change-evt (apply build-path base file))
   (λ ()
     (response/xexpr
      (format "It changed! ~a"
              (current-seconds))))))

(module+ main
  (require web-server/servlet-env)
  (serve/servlet start
                 #:servlet-regexp #rx""))

This is a Web server that tells you when a file on the server changes.
The essence is response/when that sends the response generated by
make-response once the evt is ready. You need to wait on something, so
using a synchronizable event is the most general and Rackety way.

In this example, I use filesystem-change-evt, but you could use
alarm-evt or a semaphore.

I don't think response/when is perfect because it needs to get the
meta-data somehow. It would be annoying to have to do it twice, but if
you replaced make-response with a particular response, then it
couldn't generated after the event is done, which wouldn't be as
powerful.

This is only necessary if you want the server to send SOMETHING (the
headers) before sending the content. If you didn't care about that,
then you could just replace the bottom of start with

(sync (filesystem-change-evt (apply build-path base file)))
  (response/xexpr
   (format "It changed! ~a"
           (current-seconds)))

Jay


On Wed, Dec 4, 2013 at 11:13 AM, antoine <antoine.brand at sfr.fr> wrote:
> Hello,
>
> I was wondering how can i implement a long polling ajax technique (aka
> comet) (https://en.wikipedia.org/wiki/Long_polling#Long_polling) using the
> web-server lib.
>
> So i need build a servlet that don't directly send a response but hold on in
> place and wait for being wake up.
>
> The only solution i ends up is using semaphore:
>
> #lang racket
>
> (require web-server/web-server
>          web-server/http
>          net/url
>          web-server/managers/none
>          (prefix-in seq:
>                     web-server/dispatchers/dispatch-sequencer)
>          (prefix-in filter:
>                     web-server/dispatchers/dispatch-filter)
>          web-server/servlet-dispatch)
>
> (provide
>  interface-version
>  start
>  manager)
>
> (define interface-version 'v2)
>
> (define manager
>   (create-none-manager
>    (lambda (req)
>      (response/xexpr
>       `(html (head (title "No continuation here"))
>              (body (h1 "fycj you biiii")))))))
>
> (define poll-hash (make-hash))
>
> (define (poll1 req)
>   (define params (map path/param-path (url-path (request-uri req))))
>   (define id (second params))
>   (define message (third params))
>   (with-handlers ([(lambda (_) #t) (quick-response "fail" (format "There is no ~a" id))])
>       (let ([sem (hash-ref poll-hash id)])
>         (hash-set! poll-hash id message)
>         (semaphore-post sem))
>       (quick-response "ok" "message transmitted")))
>
> (define (push1 req)
>   (define params (map path/param-path (url-path (request-uri req))))
>   (define id (first params))
>   (define sem (make-semaphore 0))
>   (hash-set! poll-hash id sem)
>   (semaphore-wait sem)
>   (begin0
>     (quick-response
>      "ok"
>      (hash-ref poll-hash id))
>     (hash-remove! poll-hash id)))
>
>
> (serve
>  #:port 8080
>  #:dispatch
>  (seq:make
>   (filter:make
>    #rx"/poll"
>    (dispatch/servlet poll1))
>   (dispatch/servlet push1)))
>
> (do-not-return)
>
> A bit of explanation of the code:
>
> - we hit push1 with a request of type /ID
> - there is no response from the server
> - we hit poll1 with a request of type /ID/MESSAGE
> - this increment the semaphore associated with ID and pass MESSAGE inside the poll-hash
> - then the previous push1 request can return
>
> Is there other way to do?
>
> I first think using a continuation, but capture continuation still return
> something and don't hold the computation.
>
> What is the cost of using a semaphore?
>
> Thanks you in advance for your responses.
> ____________________
>   Racket Users list:
>   http://lists.racket-lang.org/users



-- 
Jay McCarthy <jay at cs.byu.edu>
Assistant Professor / Brigham Young University
http://faculty.cs.byu.edu/~jay

"The glory of God is Intelligence" - D&C 93


Posted on the users mailing list.