Clojurescript long polling questions

633 views
Skip to first unread message

Dusan

unread,
Apr 30, 2012, 6:00:05 AM4/30/12
to clo...@googlegroups.com
I am trying to figure out how to setup the long polling from the clojurescript.

I use aleph on the server side. Here is the trivial aleph handler and related code:

(def k (permanent-channel))

(receive-all k (fn[x] (println "got " x)))

(defn longpoll-new [ch request]
  (siphon k ch)
  )

(defn send-mes [k mes]
  (enqueue k ((comp r/response pr-str) mes)))

On the clojurescript side I am trying to open one xhr connection, and use it for all subsequent calls:

(defn long-poll-newest
  ([url callback error-callback]
     (long-poll-newest  url callback error-callback (net/xhr-connection)))
  ([url callback error-callback xhr1]
     (do
       (event/listen xhr1
                     :success
                     (fn[e]
                       (do
                         (callback (. xhr1 (getResponseText)))
                         (long-poll-newest xhr1 url callback error-callback)
                         )))
       (event/listen xhr1
                     :error
                     (fn[e]
                       (when error-callback
                         (do
                           (println "entered the error callback")
                           (error-callback e)
                           )
                         )))
       (event/listen xhr1
                     :timeout
                     (fn[e]
                       (do
                         (long-poll-newest xhr1 url callback error-callback)
                         )
                       ))
       (net/transmit xhr1 url))
     ))

Unfiortunatelly, this receives just the first message from server, and then it stops.

This is the version that is working:
(defn long-poll [url callback error-callback]
  (let [xhr1 (net/xhr-connection)]
    (do
      (event/listen xhr1
                    :success
                    (fn[e]
                      (do
                        (callback (. xhr1 (getResponseText)))
                        (long-poll url callback error-callback)
                        )))
      (event/listen xhr1
                    :error
                    (fn[e]
                      (when error-callback
                        (do
                          (println "entered the error callback")
                          (error-callback e)
                          )
                        )))
      (event/listen xhr1
                    :timeout
                    (fn[e]
                      (do
                        (long-poll url callback error-callback)
                        )
                      ))
      (net/transmit xhr1 url))))

In this version I am closing the original xhr, I am creating the new one for each request.
How can I make the first version functional, what do I miss?

Dusan


Gijs S.

unread,
May 1, 2012, 6:17:18 AM5/1/12
to Clojure
The order of arguments you pass to long-poll-newest doesn't look
right.

You defined long-poll-newest to optionally take a fourth parameter for
the existing xhr connection. However, when calling long-poll-newest
you pass the existing xhr connection as the first argument.

Replace
(long-poll-newest xhr1 url callback error-callback)
with
(long-poll-newest url callback error-callback xhr1)

Also, all of the (do ..)'s are unnecessary. 'let', 'fn' and 'when'
evaluate the body in an implicit 'do' themselves.

The overall approach to reuse the connection looks fine otherwise.

-Gijs

Dusan Miloradovic

unread,
May 1, 2012, 7:17:40 AM5/1/12
to clo...@googlegroups.com
Unfortunaltely that does not work either, thank you for the help. It stops receiving after the first message, just like before. Here is the updated version:

(defn long-poll-newest
  ([url callback error-callback]
     (long-poll-newest  url callback error-callback (net/xhr-connection)))
  ([url callback error-callback xhr1]
     (do
       (event/listen xhr1
                     :success
                     (fn[e]
                         (callback (. xhr1 (getResponseText)))
                         (long-poll-newest  url callback error-callback xhr1)
                         ))

       (event/listen xhr1
                     :error
                     (fn[e]
                       (when error-callback
                           (println "entered the error callback")
                           (error-callback e)
                         )))
       (event/listen xhr1
                     :timeout
                     (fn[e]
                         (long-poll-newest  url callback error-callback xhr1)
                       ))
       (net/transmit xhr1 url))))

Here is the working version with opening and closing of the connection on every call, at least it should not leak xhr connections:
(defn long-poll
  ([url callback error-callback]
     (let [kk (net/xhr-connection)]
       (do
         (event/listen-once kk
                            :complete
                            (fn[e]
                              (let [isSucc (. kk (isSuccess))
                                    ek (. kk (getLastErrorCode))
                                    isErr (or (= ek ec/EXCEPTION) (= ek ec/HTTP_ERROR))]
                                (do
                                  (when isSucc
                                    (callback (. kk (getResponseText))))
                                  (. kk (dispose))
                                  (if isErr
                                    (error-callback e)
                                    (long-poll url callback error-callback))
                                  ))))
         (net/transmit kk url)))
     ))

Thx



--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Daniel Renfer

unread,
May 2, 2012, 4:36:56 PM5/2/12
to clo...@googlegroups.com
I think your issue is, you say you want long polling, but it seems
like what you're looking for is more of HTTP streaming. The result
channel you get in the aleph handler is set up to receive only a
single message and then close. If you want a streaming response,
create a new channel and a request map with that channel as it's body.
Enqueue that response map into the result channel and then siphon the
the messages from your source channel to the "body" channel.

I've found that it's best / you need to put a newline between messages
so that the response will be properly flushed.

I'm pretty sure I don't need the future, but here's how I've done it.
https://github.com/duck1123/jiksnu/blob/8d66c34f1be8b0b29b0959a18cfdc315346c2bd2/src/jiksnu/actions/stream_actions.clj#L128

Hope that helps.

Dusan Miloradovic

unread,
May 3, 2012, 2:15:50 AM5/3/12
to clo...@googlegroups.com
Hmm,

acording to the aleph documentation:

A connection to a server is represented by a result-channel. If the connection is successful, the result-channel will emit a channel that can be used to communicate with the server. When the connection closes, the channel will close.

I thought that the whole point of having the xhr instance in clojurescript is to have one "persistent" http channel, unlike the regular http.
This is the output from the console after the first(and only) response is received:

 netstat -a
Active Internet connections (including servers)
Proto Recv-Q Send-Q  Local Address          Foreign Address       (state)
..the next two are for REPL
tcp4       0      0 localhost.9000         localhost.35075        ESTABLISHED
tcp4       0      0 localhost.35075        localhost.9000         ESTABLISHED
...connection to aleph server remained open, even if the HTTP request iahs ended:
tcp4       0      0 localhost.8080         localhost.35852        ESTABLISHED
tcp4       0      0 localhost.35852        localhost.8080         ESTABLISHED

... these are for swank
tcp4       0      0 localhost.4005         localhost.51638        ESTABLISHED
tcp4       0      0 localhost.51638        localhost.4005         ESTABLISHED

Also I dont see why this:

(defn longpoll-new [ch request]
   (siphon k ch) )

closes the connection, I thought it should safely redirect all the messages from k.

What do I miss here?

Thanks for the help
Dusan

As you can see the connection from the browser remains. It does close after 5 minutes though.

Daniel Renfer

unread,
May 3, 2012, 8:33:17 PM5/3/12
to clo...@googlegroups.com
From the Lamina result-channel documentation[1]:

A result-channel represents a single potential value, as opposed to a
normal channel which represents a stream of values.

The way it works is that the result channel waits for only a single
value and then closes. If the connection is closed (you close/reload
the tab) then that result channel is also closed. This is why you're
only getting that first response.

If you want to stream responses, you have to return something that
will keep the connection open. This can be a normal lamina channel,
but I think you also have input streams, lazy sequences and functions
available. Of course, I've only really tested with returning a
channel, so I can't confirm the rest.

If you want a persistent 2-way connection, look into websockets.
You'll end up doing something rather similar with the server-side
code, but the clojurescript side can be kinda tricky as the default
Closure library didn't include Websocket support. (I believe it's
easier to get a later version in more recent releases.)

So to wrap up, the channel you get from the handler accepts only a
single message. If you want to return multiple messages, enqueue a
response map with a new channel as the :body and siphon into that
channel.

1: https://github.com/ztellman/lamina/wiki/Result-Channels

On Thu, May 3, 2012 at 2:15 AM, Dusan Miloradovic

Zach Tellman

unread,
May 4, 2012, 7:28:07 AM5/4/12
to clo...@googlegroups.com
That documentation is for the Lamina library, and describes how full-duplex client connections are represented.  If you created a TCP client, all that would be true.

However, you are writing a server, and using HTTP, which is a much more structured protocol.  Each request takes only a single response, which means that the 'ch' given to the handler function can only accept a single response value.  The XHR mechanism exists to allow long gaps between request and response, but still only supports single messages back and forth.  There exist protocols that emulate a full-duplex mechanism on top of HTTP, such as socket.io, but Aleph doesn't currently support that.

However, if you opened a WebSocket connection to the server, and set :websocket to true in the server parameters, the 'ch' *would* be a full-duplex connection.  Based on my understanding of what you're trying to do, that may be a good solution.

Zach

>> For more options, visit this group at
>> http://groups.google.com/group/clojure?hl=en
>
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your
> first post.
> To unsubscribe from this group, send email to

> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to

Dusan Miloradovic

unread,
May 5, 2012, 1:16:52 AM5/5/12
to clo...@googlegroups.com
Thanks for great answers Daniel and Zach

Dusan

Reply all
Reply to author
Forward
0 new messages