|
1278 | 1278 | #?(:clj
|
1279 | 1279 | (defn- make-client-ws-java
|
1280 | 1280 | [{:as opts :keys [uri-str headers on-error on-message on-close]}]
|
1281 |
| - (let [uri (java.net.URI. uri-str) |
1282 |
| - |
1283 |
| - ;; headers |
1284 |
| - ;; (ImmutableMap/of |
1285 |
| - ;; "Origin" "http://localhost:3200" |
1286 |
| - ;; "Referer" "http://localhost:3200" |
1287 |
| - ;; "Sec-WebSocket-Extensions" "permessage-deflate; client_max_window_bits" |
1288 |
| - ;; ) |
1289 |
| - |
1290 |
| - ws-client |
1291 |
| - (proxy [WebSocketClient] [^java.net.URI uri ^java.util.Map headers] |
1292 |
| - (onOpen [^org.java_websocket.handshake.ServerHandshake handshakedata] nil) |
1293 |
| - (onError [ex] (on-error ex)) |
1294 |
| - (onMessage [^String message] (on-message message)) |
1295 |
| - (onClose [code reason remote] (on-close code reason remote)))] |
1296 |
| - |
1297 |
| - ;; JS client attempts to connect right away at construction time. |
1298 |
| - ;; Java client doesn't need to, but we'll do anyway for consistency. |
1299 |
| - (.connect ws-client) |
1300 |
| - (do ws-client)))) |
| 1281 | + (when-let [ws-client |
| 1282 | + (try |
| 1283 | + (let [uri (java.net.URI. uri-str) |
| 1284 | + #_headers |
| 1285 | + #_ |
| 1286 | + (ImmutableMap/of |
| 1287 | + "Origin" "http://localhost:3200" |
| 1288 | + "Referer" "http://localhost:3200" |
| 1289 | + "Sec-WebSocket-Extensions" "permessage-deflate; client_max_window_bits")] |
| 1290 | + |
| 1291 | + (proxy [WebSocketClient] [^java.net.URI uri ^java.util.Map headers] |
| 1292 | + (onOpen [^org.java_websocket.handshake.ServerHandshake handshakedata] nil) |
| 1293 | + (onError [ex] (on-error ex)) |
| 1294 | + (onMessage [^String message] (on-message message)) |
| 1295 | + (onClose [code reason remote] (on-close code reason remote)))) |
| 1296 | + |
| 1297 | + (catch Throwable t |
| 1298 | + (timbre/errorf t "Error creating Java WebSocket client") |
| 1299 | + nil))] |
| 1300 | + |
| 1301 | + (delay |
| 1302 | + (.connect ws-client) |
| 1303 | + (do ws-client))))) |
1301 | 1304 |
|
1302 | 1305 | #?(:cljs
|
1303 | 1306 | (defn- make-client-ws-js
|
|
1308 | 1311 | (enc/oget goog/global "MozWebSocket")
|
1309 | 1312 | (enc/oget @?node-npm-websocket_ "w3cwebsocket"))]
|
1310 | 1313 |
|
1311 |
| - (let [socket (WebSocket. uri-str)] |
1312 |
| - (doto socket |
1313 |
| - (aset "onerror" on-error) |
1314 |
| - (aset "onmessage" on-message) ; Nb receives both push & cb evs! |
1315 |
| - ;; Fires repeatedly (on each connection attempt) while server is down: |
1316 |
| - (aset "onclose" on-close)) |
| 1314 | + (delay |
| 1315 | + (let [socket (WebSocket. uri-str)] |
| 1316 | + (doto socket |
| 1317 | + (aset "onerror" on-error) |
| 1318 | + (aset "onmessage" on-message) ; Nb receives both push & cb evs! |
| 1319 | + ;; Fires repeatedly (on each connection attempt) while server is down: |
| 1320 | + (aset "onclose" on-close)) |
1317 | 1321 |
|
1318 |
| - (when-let [bt binary-type] ; "arraybuffer" or "blob" (js default) |
1319 |
| - (aset socket "binaryType" bt)) |
1320 |
| - |
1321 |
| - socket)))) |
| 1322 | + (when-let [bt binary-type] ; "arraybuffer" or "blob" (js default) |
| 1323 | + (aset socket "binaryType" bt)) |
| 1324 | + socket))))) |
1322 | 1325 |
|
1323 | 1326 | (defn- default-client-ws-constructor
|
| 1327 | + "Returns nil if WebSocket client cannot be created, or a delay |
| 1328 | + that can be derefed to get a connected client." |
1324 | 1329 | [{:as opts :keys [on-error on-message on-close uri-str headers]}]
|
1325 | 1330 | #?(:cljs (make-client-ws-js opts)
|
1326 | 1331 | :clj (make-client-ws-java opts)))
|
|
1551 | 1556 |
|
1552 | 1557 | (retry-fn))))
|
1553 | 1558 |
|
1554 |
| - ?new-socket |
| 1559 | + ?new-socket_ |
1555 | 1560 | (try
|
1556 | 1561 | (ws-constructor
|
1557 | 1562 | (merge ws-opts
|
|
1567 | 1572 | (:csrf-token @state_))}))}))
|
1568 | 1573 |
|
1569 | 1574 | (catch #?(:clj Throwable :cljs :default) t
|
1570 |
| - (timbre/errorf t "Client WebSocket constructor error") |
| 1575 | + (timbre/errorf t "Error creating WebSocket client") |
1571 | 1576 | nil))]
|
1572 | 1577 |
|
1573 |
| - (if-let [new-socket ?new-socket] |
1574 |
| - (do |
1575 |
| - (when-let [[old-s _old-sid] |
1576 |
| - (reset-in! socket_ |
1577 |
| - [new-socket this-socket-id])] |
1578 |
| - ;; Close old socket if one exists |
1579 |
| - (timbre/tracef "Old client WebSocket will be closed") |
1580 |
| - #?(:clj (.close ^WebSocketClient old-s 1000 "CLOSE_NORMAL") |
1581 |
| - :cljs (.close old-s 1000 "CLOSE_NORMAL"))) |
1582 |
| - new-socket) |
1583 |
| - |
1584 |
| - ;; Couldn't create a socket |
1585 |
| - (retry-fn)))))] |
| 1578 | + (when-let [new-socket_ ?new-socket_] |
| 1579 | + (if-let [new-socket |
| 1580 | + (try |
| 1581 | + (force new-socket_) |
| 1582 | + (catch #?(:clj Throwable :cljs :default) t |
| 1583 | + (timbre/errorf t "Error realizing WebSocket client") |
| 1584 | + nil))] |
| 1585 | + (do |
| 1586 | + (when-let [[old-s _old-sid] (reset-in! socket_ [new-socket this-socket-id])] |
| 1587 | + ;; Close old socket if one exists |
| 1588 | + (timbre/tracef "Old client WebSocket will be closed") |
| 1589 | + #?(:clj (.close ^WebSocketClient old-s 1000 "CLOSE_NORMAL") |
| 1590 | + :cljs (.close old-s 1000 "CLOSE_NORMAL"))) |
| 1591 | + new-socket) |
| 1592 | + (retry-fn))))))] |
1586 | 1593 |
|
1587 | 1594 | (reset! retry-count_ 0)
|
1588 |
| - (connect-fn) |
1589 | 1595 |
|
1590 |
| - ;; Client-side loop to detect broken conns, Ref. #259 |
1591 |
| - (when-let [ms ws-kalive-ms] |
1592 |
| - (go-loop [] |
1593 |
| - (let [udt-t0 @udt-last-comms_] |
1594 |
| - (<! (async/timeout ms)) |
1595 |
| - (when (own-conn?) |
1596 |
| - (let [udt-t1 @udt-last-comms_] |
1597 |
| - (when-let [;; No conn send/recv activity w/in kalive window? |
1598 |
| - no-activity? (= udt-t0 udt-t1)] |
1599 |
| - |
1600 |
| - (timbre/debugf "Client will send ws-ping to server: %s" |
1601 |
| - {:ms-since-last-activity (- (enc/now-udt) udt-t1) |
1602 |
| - :timeout-ms ws-ping-timeout-ms}) |
1603 |
| - |
1604 |
| - (-chsk-send! chsk [:chsk/ws-ping] |
1605 |
| - {:flush? true |
1606 |
| - :timeout-ms ws-ping-timeout-ms |
1607 |
| - :cb ; Server will auto reply |
1608 |
| - (fn [reply] |
1609 |
| - (when (and (own-conn?) (not= reply "pong") #_(= reply :chsk/timeout)) |
1610 |
| - (timbre/debugf "Client ws-ping to server timed-out, will cycle WebSocket now") |
1611 |
| - (-chsk-reconnect! chsk :ws-ping-timeout)))}))) |
1612 |
| - (recur))))) |
1613 |
| - |
1614 |
| - chsk))) |
| 1596 | + (when (connect-fn) |
| 1597 | + |
| 1598 | + ;; Client-side loop to detect broken conns, Ref. #259 |
| 1599 | + (when-let [ms ws-kalive-ms] |
| 1600 | + (go-loop [] |
| 1601 | + (let [udt-t0 @udt-last-comms_] |
| 1602 | + (<! (async/timeout ms)) |
| 1603 | + (when (own-conn?) |
| 1604 | + (let [udt-t1 @udt-last-comms_] |
| 1605 | + (when-let [;; No conn send/recv activity w/in kalive window? |
| 1606 | + no-activity? (= udt-t0 udt-t1)] |
| 1607 | + |
| 1608 | + (timbre/debugf "Client will send ws-ping to server: %s" |
| 1609 | + {:ms-since-last-activity (- (enc/now-udt) udt-t1) |
| 1610 | + :timeout-ms ws-ping-timeout-ms}) |
| 1611 | + |
| 1612 | + (-chsk-send! chsk [:chsk/ws-ping] |
| 1613 | + {:flush? true |
| 1614 | + :timeout-ms ws-ping-timeout-ms |
| 1615 | + :cb ; Server will auto reply |
| 1616 | + (fn [reply] |
| 1617 | + (when (and (own-conn?) (not= reply "pong") #_(= reply :chsk/timeout)) |
| 1618 | + (timbre/debugf "Client ws-ping to server timed-out, will cycle WebSocket now") |
| 1619 | + (-chsk-reconnect! chsk :ws-ping-timeout)))}))) |
| 1620 | + (recur))))) |
| 1621 | + |
| 1622 | + chsk)))) |
1615 | 1623 |
|
1616 | 1624 | (defn- new-ChWebSocket [opts csrf-token]
|
1617 | 1625 | (map->ChWebSocket
|
|
1808 | 1816 | ]
|
1809 | 1817 |
|
1810 | 1818 | IChSocket
|
1811 |
| - (-chsk-disconnect! [chsk reason] |
1812 |
| - (when-let [impl @impl_] |
1813 |
| - (-chsk-disconnect! impl reason))) |
1814 |
| - |
1815 |
| - ;; Possibly reset impl type: |
1816 |
| - (-chsk-reconnect! [chsk reason] |
1817 |
| - (when-let [impl @impl_] |
1818 |
| - (-chsk-disconnect! impl reason) |
1819 |
| - (-chsk-connect! chsk))) |
1820 |
| - |
1821 |
| - (-chsk-break-connection! [chsk opts] |
1822 |
| - (when-let [impl @impl_] |
1823 |
| - (-chsk-break-connection! impl opts))) |
| 1819 | + (-chsk-break-connection! [chsk opts] (when-let [impl @impl_] (-chsk-break-connection! impl opts))) |
| 1820 | + (-chsk-disconnect! [chsk reason] (when-let [impl @impl_] (-chsk-disconnect! impl reason))) |
| 1821 | + (-chsk-reconnect! [chsk reason] |
| 1822 | + (-chsk-disconnect! chsk reason) |
| 1823 | + (-chsk-connect! chsk)) |
1824 | 1824 |
|
1825 | 1825 | (-chsk-send! [chsk ev opts]
|
1826 | 1826 | (if-let [impl @impl_]
|
|
1830 | 1830 | (chsk-send->closed! ?cb-fn))))
|
1831 | 1831 |
|
1832 | 1832 | (-chsk-connect! [chsk]
|
1833 |
| - ;; Starting with a simple downgrade-only strategy here as a proof of concept |
1834 |
| - ;; TODO Later consider smarter downgrade or downgrade+upgrade strategies? |
| 1833 | + ;; Currently using a simplistic downgrade-only strategy. |
| 1834 | + ;; TODO Consider smarter strategy that can also upgrade? |
1835 | 1835 | (let [ajax-chsk-opts (assoc ajax-chsk-opts :state_ state_)
|
1836 |
| - ws-chsk-opts (assoc ws-chsk-opts :state_ state_) |
| 1836 | + ws-chsk-opts (assoc ws-chsk-opts :state_ state_) |
1837 | 1837 |
|
1838 |
| - ajax-conn! |
| 1838 | + ajax-chsk! |
1839 | 1839 | (fn []
|
1840 |
| - ;; Remove :auto->:ajax downgrade watch |
1841 |
| - (remove-watch state_ :chsk/auto-ajax-downgrade) |
1842 |
| - (-chsk-connect! (new-ChAjaxSocket ajax-chsk-opts (:csrf-token @state_)))) |
| 1840 | + (let [ajax-chsk (new-ChAjaxSocket ajax-chsk-opts (:csrf-token @state_))] |
| 1841 | + (remove-watch state_ :chsk/auto-ajax-downgrade) |
| 1842 | + (-chsk-connect! ajax-chsk))) |
1843 | 1843 |
|
1844 |
| - ws-conn! |
| 1844 | + ws-chsk! |
1845 | 1845 | (fn []
|
1846 |
| - ;; Configure :auto->:ajax downgrade watch |
1847 |
| - (let [downgraded?_ (atom false)] |
| 1846 | + (let [ws-chsk (new-ChWebSocket ws-chsk-opts (:csrf-token @state_)) |
| 1847 | + downgraded?_ (atom false)] |
| 1848 | + |
1848 | 1849 | (add-watch state_ :chsk/auto-ajax-downgrade
|
1849 | 1850 | (fn [_ _ old-state new-state]
|
1850 |
| - (when-let [impl @impl_] |
1851 |
| - (when-let [ever-opened?_ (:ever-opened?_ impl)] |
1852 |
| - (when-not @ever-opened?_ |
1853 |
| - (when (:last-ws-error new-state) |
1854 |
| - (when (compare-and-set! downgraded?_ false true) |
1855 |
| - (timbre/warnf "Client permanently downgrading chsk mode: :auto -> :ajax") |
1856 |
| - (-chsk-disconnect! impl :downgrading-ws-to-ajax) |
1857 |
| - (reset! impl_ (ajax-conn!)))))))))) |
1858 |
| - |
1859 |
| - (-chsk-connect! (new-ChWebSocket ws-chsk-opts (:csrf-token @state_))))] |
1860 |
| - |
1861 |
| - (reset! impl_ (or (ws-conn!) (ajax-conn!))) |
| 1851 | + (enc/when-let [state-changed? (not= old-state new-state) |
| 1852 | + impl @impl_ |
| 1853 | + ever-opened?_ (:ever-opened?_ impl) |
| 1854 | + never-opened? (not @ever-opened?_) |
| 1855 | + ws-error (:last-ws-error new-state)] |
| 1856 | + |
| 1857 | + (when (compare-and-set! downgraded?_ false true) |
| 1858 | + (timbre/warnf "Client permanently downgrading chsk mode: :auto -> :ajax") |
| 1859 | + (-chsk-disconnect! impl :downgrading-ws-to-ajax) |
| 1860 | + (reset! impl_ (ajax-chsk!)))))) |
| 1861 | + |
| 1862 | + (-chsk-connect! ws-chsk)))] |
| 1863 | + |
| 1864 | + (reset! impl_ (or (ws-chsk!) (ajax-chsk!))) |
1862 | 1865 | chsk))))
|
1863 | 1866 |
|
1864 | 1867 | #?(:cljs
|
|
1911 | 1914 | ; await reply before regarding the connection as broken
|
1912 | 1915 |
|
1913 | 1916 | :ws-constructor ; Advanced, (fn [{:keys [uri-str headers on-message on-error on-close]}]
|
1914 |
| - ; => connected WebSocket, see `default-client-ws-constructor` code for |
1915 |
| - ; details." |
| 1917 | + ; => nil, or delay that can be dereffed to get a connected WebSocket. |
| 1918 | + ; See `default-client-ws-constructor` code for details." |
1916 | 1919 |
|
1917 | 1920 | [path ?csrf-token-or-fn &
|
1918 | 1921 | [{:as opts
|
|
0 commit comments