All Downloads are FREE. Search and download functionalities are using the official Maven repository.

webapp.sse-webapp.venice Maven / Gradle / Ivy

There is a newer version: 1.12.34
Show newest version
;; -----------------------------------------------------------------------------
;; Demo Server-Side-Events Service
;; -----------------------------------------------------------------------------
;;

(load-module :tomcat ['tomcat :as 'tc])
(load-module :ring)
(load-module :server-side-events ['server-side-events :as 'sse])

(import :java.io.IOException)


; ensure the Tomcat libs are on the classpath
(tc/check-required-libs)



;; -----------------------------------------------------------------------------
;; Event Producer
;; -----------------------------------------------------------------------------

(defn event-producer [queue stop last-event-id]
  (let [start-id   (if (and (some? last-event-id) 
                            (match? last-event-id "[1-9][0-9]*"))
                     (long last-event-id) 
                     1000)
        id-counter (atom start-id)]
    (println "Event producer running, starting at" start-id)
    (thread #(try
              (loop []
                (when-not @stop 
                  (let [id (str @id-counter)]
                    (swap! id-counter inc) ;; next id
                    (sleep 2000)
                    (when-not @stop
                      (println "Adding event " id)
                      (put! queue { :id    id 
                                    :event "demo" 
                                    :data  ["Counter ~{id}"] })
                      (recur)))))
              (finally 
                (println "Event producer stopped."))))))



;; -----------------------------------------------------------------------------
;; Ring handler (publishes the events to the client)
;; -----------------------------------------------------------------------------

(defn events-demo [queue request]
  ;; If a connection drops, the client can send a "Last-Event-ID" HTTP header  
  ;; with its last received id value to the server upon reconnecting, allowing  
  ;; the server to determine where to resume the event stream.
  (let [last-event-id   (ring-util/get-request-header request "Last-Event-ID")
        max-count       (ring-util/get-request-long-parameter request "max-count" nil)
        stop-events     (atom false)
        async_request   (:async-request request)   ;; HttpServletRequest
        async_response  (:async-response request)  ;; HttpServletResponse
        os              (. async_response :getOutputStream)
        pr              (io/wrap-os-with-print-writer os :utf-8)]

    (. async_response :setContentType       "text/event-stream")
    (. async_response :setCharacterEncoding "UTF-8")

    (println "Last-Event-ID: " last-event-id)
    (println "Max count: " max-count)

    ;; start the event producer
    (event-producer queue stop-events last-event-id)

    (try
      (loop [counter 0]
        (if (or (nil? max-count) (< counter max-count))
          (let [event       (take! queue)
                sse-event   (sse/render event)]
            (println "Publishing event: " (:id event))
            (print pr sse-event)
            (flush pr)
            (recur (inc counter)))
          (println "Event max count of" max-count "reached")))
      (catch [:cause-type :java.io.IOException] e 
         (println "Client closed connection." (ex-message e)))
      (catch :Exception e 
        (println "Stopped. Error:" (ex-message e))))
            
    ;; stop the event producer
    (reset! stop-events true)
    (empty queue)

    (println "Stopped serving events")

    ;; return nil to signal the :ring module to not send any further data
    ;; to the client!
    nil))



;; -----------------------------------------------------------------------------
;; Middleware configuration
;; -----------------------------------------------------------------------------

(def sse-queue (queue))

(def routes [[:get "/events"  (partial events-demo sse-queue)]])


(defn events-servlet []
  (ring/create-servlet (-> (ring/match-routes routes)     ; >--+
                                                          ;    |
                           (ring-mw/mw-dump-response)     ; ^  |
                           (ring-mw/mw-dump-request)      ; |  |
                           (ring-mw/mw-request-counter)   ; |  |
                           (ring-mw/mw-add-session 3600)  ; |  |
                           (ring-mw/mw-print-uri)         ; |  |
                           (ring-mw/mw-debug :on))))      ; +--+

;; Tomcat server options
(def tomcat-opts { :await?    false   ;; do not block - return after start
                   :base-dir  "."
                   :port      8080 })

;; start Tomcat (wires Tomcat with the ring servlets)
(let [server (tc/start [ [ (events-servlet)
                           { :name          "events-servlet"  
                             :mapping       "/*"
                             :async-support true } ] ]
                       tomcat-opts)]
  (defn stop [] (tc/shutdown server)))

;; -----------------------------------------------------------------------------

(println "Tomcat started on port ~(:port tomcat-opts).")
(println "Stop it by calling:  (stop)")




;; -----------------------------------------------------------------------------
;; Venice HTTP Client examples
;; -----------------------------------------------------------------------------

;; GET (demo)
(comment
  ;; run this Http client in another REPL. It kicks off server side streaming
  ;; of events and displays the received events. Stops the connection after
  ;; having received 10 events.
  ;; Connection close is initated by client
  (do
    (load-module :http-client-j8 ['http-client-j8 :as 'hc])

    (let [response  (hc/send :get 
                             "http://localhost:8080/events" 
                             :headers { "Accept"         "text/event-stream" 
                                        "Cache-Control"  "no-cache"
                                        "Connection"     "keep-alive" }
                             :conn-timeout 0
                             :read-timeout 0
                             :debug true)]
      (println "Status:" (:http-status response))

      (hc/process-server-side-events 
        response
        (fn [type event event-count]
          (case type
            :opened (do (println "\nStreaming started")
                        :ok)
            :data   (do (println "Event: " (pr-str event))
                        ;; only process 10 events
                        (if (< event-count 10) :ok :stop))
            :closed (do (println "Streaming closed")
                        :ok))))))
)

;; GET (demo)
(comment
  ;; run this Http client in another REPL. It kicks off server side streaming
  ;; of events and displays the received events. The server will close the 
  ;; connection after having sent 6 events due to the uri query parameter 
  ;; "max-count=6". The client reacts on the closed connection by stopping
  ;; processing events.
  ;; Connection close is initated by server
  (do
    (load-module :http-client-j8 ['http-client-j8 :as 'hc])

    (let [response  (hc/send :get 
                             "http://localhost:8080/events?max-count=6" 
                             :headers { "Accept"         "text/event-stream" 
                                        "Cache-Control"  "no-cache"
                                        "Connection"     "keep-alive" }
                             :conn-timeout 0
                             :read-timeout 0
                             :debug true)]
      (println "Status:" (:http-status response))

      (hc/process-server-side-events 
        response
        (fn [type event event-count]
          (case type
            :opened (do (println "\nStreaming started")
                        :ok)
            :data   (do (println "Event: " (pr-str event))
                        ;; only process 10 events
                        (if (< event-count 10) :ok :stop))
            :closed (do (println "Streaming closed")
                        :ok))))))
)

;; GET (demo)
(comment
  ;; run this Http client in another REPL. It kicks off server side streaming
  ;; of events at the "Last-Event-Id" 5000 and displays the received events. 
  ;; Stops the connection after having received 10 events.
  ;; Connection close is initated by client
  (do
    (load-module :http-client-j8 ['http-client-j8 :as 'hc])

    (let [response  (hc/send :get 
                             "http://localhost:8080/events" 
                             :headers { "Accept"         "text/event-stream" 
                                        "Cache-Control"  "no-cache"
                                        "Connection"     "keep-alive"
                                        "Last-Event-ID"  "5000"}
                             :conn-timeout 0
                             :read-timeout 0
                             :debug true)]
      (println "Status:" (:http-status response))

      (hc/process-server-side-events 
        response
        (fn [type event event-count]
          (case type
            :opened (do (println "\nStreaming started")
                        :ok)
            :data   (do (println "Event: " (pr-str event))
                        ;; only process 10 events
                        (if (< event-count 10) :ok :stop))
            :closed (do (println "Streaming closed")
                        :ok))))))
)





© 2015 - 2024 Weber Informatics LLC | Privacy Policy