|
35 | 35 | ChannelPipeline]
|
36 | 36 | [io.netty.handler.codec
|
37 | 37 | TooLongFrameException]
|
38 |
| - [io.netty.handler.stream |
| 38 | + [io.netty.handler.timeout |
| 39 | + IdleState |
| 40 | + IdleStateEvent] |
| 41 | + [io.netty.handler.stream |
39 | 42 | ChunkedWriteHandler]
|
40 |
| - [io.netty.handler.codec.http |
| 43 | + [io.netty.handler.codec.http |
41 | 44 | FullHttpRequest]
|
42 | 45 | [io.netty.handler.codec.http.websocketx
|
43 | 46 | CloseWebSocketFrame
|
|
62 | 65 | [io.netty.handler.logging
|
63 | 66 | LoggingHandler
|
64 | 67 | LogLevel]
|
| 68 | + [java.util.concurrent |
| 69 | + ConcurrentLinkedQueue] |
65 | 70 | [java.util.concurrent.atomic
|
66 | 71 | AtomicInteger]
|
67 | 72 | [aleph.utils
|
|
603 | 608 | (doto (DefaultHttpHeaders.) (http/map->headers! headers))
|
604 | 609 | max-frame-payload))
|
605 | 610 |
|
606 |
| -(defn websocket-client-handler [raw-stream? uri sub-protocols extensions? headers max-frame-payload] |
607 |
| - (let [d (d/deferred) |
608 |
| - in (atom nil) |
609 |
| - desc (atom {}) |
610 |
| - handshaker (websocket-handshaker uri sub-protocols extensions? headers max-frame-payload)] |
| 611 | +(defn websocket-client-handler |
| 612 | + ([raw-stream? |
| 613 | + uri |
| 614 | + sub-protocols |
| 615 | + extensions? |
| 616 | + headers |
| 617 | + max-frame-payload] |
| 618 | + (websocket-client-handler raw-stream? |
| 619 | + uri |
| 620 | + sub-protocols |
| 621 | + extensions? |
| 622 | + headers |
| 623 | + max-frame-payload |
| 624 | + nil)) |
| 625 | + ([raw-stream? |
| 626 | + uri |
| 627 | + sub-protocols |
| 628 | + extensions? |
| 629 | + headers |
| 630 | + max-frame-payload |
| 631 | + heartbeats] |
| 632 | + (let [d (d/deferred) |
| 633 | + in (atom nil) |
| 634 | + desc (atom {}) |
| 635 | + ^ConcurrentLinkedQueue pending-pings (ConcurrentLinkedQueue.) |
| 636 | + handshaker (websocket-handshaker uri sub-protocols extensions? headers max-frame-payload)] |
611 | 637 |
|
612 |
| - [d |
| 638 | + [d |
613 | 639 |
|
614 |
| - (netty/channel-inbound-handler |
| 640 | + (netty/channel-inbound-handler |
615 | 641 |
|
616 | 642 | :exception-caught
|
617 | 643 | ([_ ctx ex]
|
618 |
| - (when-not (d/error! d ex) |
619 |
| - (log/warn ex "error in websocket client")) |
620 |
| - (s/close! @in) |
621 |
| - (netty/close ctx)) |
| 644 | + (when-not (d/error! d ex) |
| 645 | + (log/warn ex "error in websocket client")) |
| 646 | + (s/close! @in) |
| 647 | + (netty/close ctx)) |
622 | 648 |
|
623 | 649 | :channel-inactive
|
624 | 650 | ([_ ctx]
|
625 |
| - (when (realized? d) |
626 |
| - ;; close only on success |
627 |
| - (d/chain' d s/close!)) |
628 |
| - (.fireChannelInactive ctx)) |
| 651 | + (when (realized? d) |
| 652 | + ;; close only on success |
| 653 | + (d/chain' d s/close!)) |
| 654 | + (http/resolve-pings! pending-pings false) |
| 655 | + (.fireChannelInactive ctx)) |
629 | 656 |
|
630 | 657 | :channel-active
|
631 | 658 | ([_ ctx]
|
632 |
| - (let [ch (.channel ctx)] |
633 |
| - (reset! in (netty/buffered-source ch (constantly 1) 16)) |
634 |
| - (.handshake handshaker ch)) |
635 |
| - (.fireChannelActive ctx)) |
| 659 | + (let [ch (.channel ctx)] |
| 660 | + (reset! in (netty/buffered-source ch (constantly 1) 16)) |
| 661 | + (.handshake handshaker ch)) |
| 662 | + (.fireChannelActive ctx)) |
| 663 | + |
| 664 | + :user-event-triggered |
| 665 | + ([_ ctx evt] |
| 666 | + (if (and (instance? IdleStateEvent evt) |
| 667 | + (= IdleState/ALL_IDLE (.state ^IdleStateEvent evt))) |
| 668 | + (when (d/realized? d) |
| 669 | + (http/handle-heartbeat ctx @d heartbeats)) |
| 670 | + (.fireUserEventTriggered ctx evt))) |
636 | 671 |
|
637 | 672 | :channel-read
|
638 | 673 | ([_ ctx msg]
|
639 |
| - (try |
640 |
| - (let [ch (.channel ctx)] |
641 |
| - (cond |
642 |
| - |
643 |
| - (not (.isHandshakeComplete handshaker)) |
644 |
| - (-> (netty/wrap-future (.processHandshake handshaker ch msg)) |
645 |
| - (d/chain' |
646 |
| - (fn [_] |
647 |
| - (let [out (netty/sink ch false |
648 |
| - (fn [c] |
649 |
| - (if (instance? CharSequence c) |
650 |
| - (TextWebSocketFrame. (bs/to-string c)) |
651 |
| - (BinaryWebSocketFrame. (netty/to-byte-buf ctx c)))) |
652 |
| - (fn [] @desc))] |
653 |
| - |
654 |
| - (d/success! d |
655 |
| - (doto |
| 674 | + (try |
| 675 | + (let [ch (.channel ctx)] |
| 676 | + (cond |
| 677 | + |
| 678 | + (not (.isHandshakeComplete handshaker)) |
| 679 | + (-> (netty/wrap-future (.processHandshake handshaker ch msg)) |
| 680 | + (d/chain' |
| 681 | + (fn [_] |
| 682 | + (let [out (netty/sink ch false |
| 683 | + (http/websocket-message-coerce-fn ch pending-pings) |
| 684 | + (fn [] @desc))] |
| 685 | + |
| 686 | + (s/on-closed out (fn [] (http/resolve-pings! pending-pings false))) |
| 687 | + |
| 688 | + (d/success! d |
| 689 | + (doto |
656 | 690 | (s/splice out @in)
|
657 |
| - (reset-meta! {:aleph/channel ch}))) |
658 |
| - |
659 |
| - (s/on-drained @in |
660 |
| - #(when (.isOpen ch) |
661 |
| - (d/chain' |
662 |
| - (netty/wrap-future (.close handshaker ch (CloseWebSocketFrame.))) |
663 |
| - (fn [_] (netty/close ctx)))))))) |
664 |
| - (d/catch' |
665 |
| - (fn [ex] |
666 |
| - ;; handle handshake exception |
667 |
| - (d/error! d ex) |
668 |
| - (s/close! @in) |
669 |
| - (netty/close ctx)))) |
670 |
| - |
671 |
| - (instance? FullHttpResponse msg) |
672 |
| - (let [rsp ^FullHttpResponse msg] |
673 |
| - (throw |
674 |
| - (IllegalStateException. |
675 |
| - (str "unexpected HTTP response, status: " |
| 691 | + (reset-meta! {:aleph/channel ch}))) |
| 692 | + |
| 693 | + (s/on-drained @in |
| 694 | + #(when (.isOpen ch) |
| 695 | + (d/chain' |
| 696 | + (netty/wrap-future (.close handshaker ch (CloseWebSocketFrame.))) |
| 697 | + (fn [_] (netty/close ctx)))))))) |
| 698 | + (d/catch' |
| 699 | + (fn [ex] |
| 700 | + ;; handle handshake exception |
| 701 | + (d/error! d ex) |
| 702 | + (s/close! @in) |
| 703 | + (netty/close ctx)))) |
| 704 | + |
| 705 | + (instance? FullHttpResponse msg) |
| 706 | + (let [rsp ^FullHttpResponse msg] |
| 707 | + (throw |
| 708 | + (IllegalStateException. |
| 709 | + (str "unexpected HTTP response, status: " |
676 | 710 | (.status rsp)
|
677 | 711 | ", body: '"
|
678 | 712 | (bs/to-string (.content rsp))
|
679 | 713 | "'"))))
|
680 | 714 |
|
681 |
| - (instance? TextWebSocketFrame msg) |
682 |
| - (netty/put! ch @in (.text ^TextWebSocketFrame msg)) |
| 715 | + (instance? TextWebSocketFrame msg) |
| 716 | + (netty/put! ch @in (.text ^TextWebSocketFrame msg)) |
683 | 717 |
|
684 |
| - (instance? BinaryWebSocketFrame msg) |
685 |
| - (let [frame (.content ^BinaryWebSocketFrame msg)] |
686 |
| - (netty/put! ch @in |
687 |
| - (if raw-stream? |
688 |
| - (netty/acquire frame) |
689 |
| - (netty/buf->array frame)))) |
| 718 | + (instance? BinaryWebSocketFrame msg) |
| 719 | + (let [frame (.content ^BinaryWebSocketFrame msg)] |
| 720 | + (netty/put! ch @in |
| 721 | + (if raw-stream? |
| 722 | + (netty/acquire frame) |
| 723 | + (netty/buf->array frame)))) |
690 | 724 |
|
691 |
| - (instance? PongWebSocketFrame msg) |
692 |
| - nil |
| 725 | + (instance? PongWebSocketFrame msg) |
| 726 | + (http/resolve-pings! pending-pings true) |
693 | 727 |
|
694 |
| - (instance? PingWebSocketFrame msg) |
695 |
| - (let [frame (.content ^PingWebSocketFrame msg)] |
696 |
| - (netty/write-and-flush ch (PongWebSocketFrame. (netty/acquire frame)))) |
| 728 | + (instance? PingWebSocketFrame msg) |
| 729 | + (let [frame (.content ^PingWebSocketFrame msg)] |
| 730 | + (netty/write-and-flush ch (PongWebSocketFrame. (netty/acquire frame)))) |
697 | 731 |
|
698 |
| - (instance? CloseWebSocketFrame msg) |
699 |
| - (let [frame ^CloseWebSocketFrame msg] |
700 |
| - (when (realized? d) |
701 |
| - (swap! desc assoc |
702 |
| - :websocket-close-code (.statusCode frame) |
703 |
| - :websocket-close-msg (.reasonText frame))) |
704 |
| - (netty/close ctx)) |
| 732 | + (instance? CloseWebSocketFrame msg) |
| 733 | + (let [frame ^CloseWebSocketFrame msg] |
| 734 | + (when (realized? d) |
| 735 | + (swap! desc assoc |
| 736 | + :websocket-close-code (.statusCode frame) |
| 737 | + :websocket-close-msg (.reasonText frame))) |
| 738 | + (netty/close ctx)) |
705 | 739 |
|
706 |
| - :else |
707 |
| - (.fireChannelRead ctx msg))) |
708 |
| - (finally |
709 |
| - (netty/release msg)))))])) |
| 740 | + :else |
| 741 | + (.fireChannelRead ctx msg))) |
| 742 | + (finally |
| 743 | + (netty/release msg)))))]))) |
710 | 744 |
|
711 | 745 | (defn websocket-connection
|
712 | 746 | [uri
|
|
722 | 756 | extensions?
|
723 | 757 | max-frame-payload
|
724 | 758 | max-frame-size
|
725 |
| - compression?] |
| 759 | + compression? |
| 760 | + heartbeats] |
726 | 761 | :or {bootstrap-transform identity
|
727 | 762 | pipeline-transform identity
|
728 | 763 | raw-stream? false
|
|
736 | 771 | scheme (.getScheme uri)
|
737 | 772 | _ (assert (#{"ws" "wss"} scheme) "scheme must be one of 'ws' or 'wss'")
|
738 | 773 | ssl? (= "wss" scheme)
|
| 774 | + heartbeats (when (some? heartbeats) |
| 775 | + (merge |
| 776 | + {:send-after-idle 3e4 |
| 777 | + :payload nil |
| 778 | + :timeout nil} |
| 779 | + heartbeats)) |
739 | 780 | [s handler] (websocket-client-handler
|
740 | 781 | raw-stream?
|
741 | 782 | uri
|
742 | 783 | sub-protocols
|
743 | 784 | extensions?
|
744 | 785 | headers
|
745 |
| - max-frame-payload)] |
| 786 | + max-frame-payload |
| 787 | + heartbeats)] |
746 | 788 | (d/chain'
|
747 | 789 | (netty/create-client
|
748 | 790 | (fn [^ChannelPipeline pipeline]
|
|
754 | 796 | (.addLast ^ChannelPipeline %
|
755 | 797 | "websocket-deflater"
|
756 | 798 | WebSocketClientCompressionHandler/INSTANCE)))
|
| 799 | + (http/attach-heartbeats-handler heartbeats) |
757 | 800 | (.addLast "handler" ^ChannelHandler handler)
|
758 | 801 | pipeline-transform))
|
759 | 802 | (when ssl?
|
|
0 commit comments