Skip to content

write-body-to-stream does not behave like in ring.adapter.jetty #122

Description

@chpill

Hello, first of all, thank you for this library! I am considering using it
instead of the original adapter for a web project, but I ran into an issue with
an SSE handler. Here is a barebone handler that demonstrates the issue:

(Tested with Clojure 1.12.0-alpha5 and Java 21)

{:deps {org.clojure/clojure {:mvn/version "1.12.0-alpha5"}
        ;; info.sunng/ring-jetty9-adapter {:mvn/version "0.31.0"}
        ring/ring-jetty-adapter {:mvn/version "1.11.0"}
        }}
(ns sse-with-ring
  (:require [clojure.java.io :as io]
            ;; [ring.adapter.jetty9 :refer [run-jetty]]
            [ring.adapter.jetty :refer [run-jetty]]
            [ring.core.protocols]))

(defn handler [_]
  {:status 200
   :body (reify
           ring.core.protocols/StreamableResponseBody
           (write-body-to-stream [_ _ output-stream]
             (let [writer (io/writer output-stream)]
               (future (loop [i 10]
                         (doto writer (.write (str "data: " i "\n")) .flush)
                         (if (zero? i)
                           (.close writer)
                           (do (Thread/sleep 100)
                               (recur (dec i)))))))))})

(def server (run-jetty (fn [req respond raise]
                         (try (respond (handler req))
                              (catch Exception e (raise e))))
                       {:port 12345
                        :async? true
                        :join? false}))

(comment (.stop server))

With ring/ring-jetty-adapter {:mvn/version "1.11.0"}, the endpoint behaves as
expected, streaming each line as soon as they are "produced":

curl -Nv localhost:12345
*   Trying [::1]:12345...
* Connected to localhost (::1) port 12345
> GET / HTTP/1.1
> Host: localhost:12345
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Wed, 10 Jan 2024 16:14:34 GMT
< Transfer-Encoding: chunked
< Server: Jetty(11.0.18)
<
data: 10
data: 9
data: 8
data: 7
data: 6
data: 5
data: 4
data: 3
data: 2
data: 1
data: 0
* Connection #0 to host localhost left intact

But with info.sunng/ring-jetty9-adapter {:mvn/version "0.31.0"}, curl returns
immediately without displaying any of our "data: x" messages:

curl -Nv localhost:12345
*   Trying [::1]:12345...
* Connected to lol (::1) port 12345
> GET / HTTP/1.1
> Host: lol:12345
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Wed, 10 Jan 2024 15:58:38 GMT
< Content-Length: 0
< Server: Jetty(11.0.18)
<
* Connection #0 to host lol left intact

I found out an issue explaining that it is perfectly valid for a ring
handler to return while the output-stream remains open, and it may be closed
later in another thread.

Note that this streaming handler only works in the original adapter with the
:async? true setting. Reading the spec, it is not evident to me why this is
the case, and it's a bit unfortunate as we have to wrap our otherwise completely
synchronous ring application with:

(fn [req respond raise]
  (try (respond (handler req))
       (catch Exception e (raise e))))

Maybe @weavejester could clarify why this is the case?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions