Essentially, I just wanted to be able to stream data out of my
clojure-based http app. The current methods of returning a body to
ring are either all in memory (String, etc) or I have to wrap
everything in an input stream (which results in really weird looking
code in my application as I have to figure out how many bytes I've
serialized and return them and all that stuff in order to support the
contracts on InputStreams methods.
I opted to add another payload type to the body of a return (i.e. the
:body of the map) which is an IFn. The servlet code just opens up the
output stream and passes it into the IFn, which then presumably puts
bytes in. This doesn't change the synchronaiety/asynchronaiety of
ring and generally just gives applications the flexibility to work
with streams if they want to. I think it's a relatively unobtrusive
change (just a new object type allowed as the :body of the return
value) and would like to stop playing version games in order to have
the functionality locally ;). Please accept and all that jazz...
The changes are at http://github.com/cheddar/ring (It's actually a
3-line change but I just realized I apparently did some whiteline
changes as well. I can eliminate those if you guys prefer...)
--Eric
If you had told me only this much, I would not guess that the IFn was
supposed to accept one argument, an OutputStream, and returns nothing.
This is my main concern, than there is another, better use for IFn in
the response, and that this change would make it more difficult to
experiment with or adopt the better alternative later.
> Essentially, I just wanted to be able to stream data out of my
> clojure-based http app. The current methods of returning a body to
> ring are either all in memory (String, etc) or I have to wrap
> everything in an input stream (which results in really weird looking
> code in my application as I have to figure out how many bytes I've
> serialized and return them and all that stuff in order to support the
> contracts on InputStreams methods.
What about using a lazy sequence of strings as the body? This is
already supported by ring, and it is not clear to me why the IFn
option would be more convenient. Do you have some small examples of
this issue?
> I opted to add another payload type to the body of a return (i.e. the
> :body of the map) which is an IFn. The servlet code just opens up the
> output stream and passes it into the IFn, which then presumably puts
> bytes in. This doesn't change the synchronaiety/asynchronaiety of
> ring and generally just gives applications the flexibility to work
> with streams if they want to. I think it's a relatively unobtrusive
> change (just a new object type allowed as the :body of the return
> value) and would like to stop playing version games in order to have
> the functionality locally ;). Please accept and all that jazz...
When there is a cond with many cases, and someone wants to add a case
for their application, it sounds like maybe that cond should be
replaced with a multimethod or protocol. This lets us defer the
decision as to how IFn should be treated when it appears in the body,
while letting you have your immediate convenience and allowing others
to experiment with alternatives. Does this seem like a good solution?
This change is somewhat problematic. First, it would require updating
the SPEC, which is not something to be done lightly. Second, there are
already some tentative ideas to use the IFn return type to support
asynchronous HTTP responses.
I also don't think this proposed change adds any new functionality. As
far as I can tell, you can achieve the same results with a lazy seq.
- James
If by the HTTP response object you mean HttpServletResponse, then I
don't think the function should take this, because it ties Ring to the
Servlet specification.
Custom content streaming can currently be achieved with a lazy seq, or
by returning an InputStream. A lazy seq seems a more idiomatic Clojure
approach to streaming data.
Additionally, one could use an asynchronous HTTP function to stream
data, if one just uses the 'send' part. Here is one possible approach:
(defn handler [request]
(fn [send]
(send "Hello")
(send "Streaming")
(send "World")))
- James