websocket: Gracefully handle read errors and socket close.
authorJan (janneke) Nieuwenhuizen <janneke@gnu.org>
Sun, 8 Nov 2020 07:30:39 +0000 (08:30 +0100)
committerJan (janneke) Nieuwenhuizen <janneke@gnu.org>
Sun, 8 Nov 2020 07:30:39 +0000 (08:30 +0100)
* 8sync/systems/websocket/client.scm (websocket-socket-open?): New
method.
(websocket-loop): Use it to gracefully handle errors socket close.

8sync/systems/websocket/client.scm

index 4ae342d9c919f776b8ff3498ffff23999e79458b..36ee4a9aae83f281daaa478eb0adcde3fe0147c6 100644 (file)
 (define-method (websocket-init (websocket <websocket>) message)
   (and=> (.url websocket) (cut websocket-open websocket message <>)))
 
+(define-method (websocket-socket-open? (websocket <websocket>))
+  "Return #t if .SOCKET of WEBSOCKET is open."
+  (not (port-closed? (.socket websocket))))
+
 (define-method (websocket-loop (websocket <websocket>) message)
 
   (define (handle-data-frame type data)
 
   (let loop ((fragments '())
              (type #f))
-    (let* ((socket (.socket websocket))
-           (frame (and (websocket-open? websocket)
-                       (read-frame-maybe))))
-      (cond
-       ;; EOF - port is closed.
-       ;; @@: Sometimes the eof object appears here as opposed to
-       ;;   at lookahead, but I'm not sure why
-       ((or (not frame) (eof-object? frame))
-        (close-down))
-       ;; Per section 5.4, control frames may appear interspersed
-       ;; along with a fragmented message.
-       ((close-frame? frame)
-        ;; Per section 5.5.1, echo the close frame back to the
-        ;; socket before closing the socket.  The socket may no
-        ;; longer be listening.
-        (false-if-exception
-         (write-frame (make-close-frame (frame-data frame)) socket))
-        (close-down))
-       ((ping-frame? frame)
-        ;; Per section 5.5.3, a pong frame must include the exact
-        ;; same data as the ping frame.
-        (write-frame (make-pong-frame (frame-data frame)) socket)
-        (loop fragments type))
-       ((pong-frame? frame)           ; silently ignore pongs
-        (loop fragments type))
-       ((first-fragment-frame? frame) ; begin accumulating fragments
-        (loop (list frame) (frame-type frame)))
-       ((final-fragment-frame? frame) ; concatenate all fragments
-        (handle-data-frame type (frame-concatenate
-                                 (reverse (cons frame fragments))))
-        (loop '() #f))
-       ((fragment-frame? frame)       ; add a fragment
-        (loop (cons frame fragments) type))
-       ((data-frame? frame)           ; unfragmented data frame
-        (handle-data-frame (frame-type frame) (frame-data frame))
-        (loop '() #f))))))
+    (catch #t
+      (lambda _
+        (let* ((socket (.socket websocket))
+               (frame (and (websocket-open? websocket)
+                           (read-frame-maybe))))
+          (cond
+           ;; EOF - port is closed.
+           ;; @@: Sometimes the eof object appears here as opposed to
+           ;;   at lookahead, but I'm not sure why
+           ((or (not frame) (eof-object? frame))
+            (close-down))
+           ;; Per section 5.4, control frames may appear interspersed
+           ;; along with a fragmented message.
+           ((close-frame? frame)
+            ;; Per section 5.5.1, echo the close frame back to the
+            ;; socket before closing the socket.  The socket may no
+            ;; longer be listening.
+            (false-if-exception
+             (write-frame (make-close-frame (frame-data frame)) socket))
+            (close-down))
+           ((ping-frame? frame)
+            ;; Per section 5.5.3, a pong frame must include the exact
+            ;; same data as the ping frame.
+            (write-frame (make-pong-frame (frame-data frame)) socket)
+            (loop fragments type))
+           ((pong-frame? frame)         ; silently ignore pongs
+            (loop fragments type))
+           ((first-fragment-frame? frame) ; begin accumulating fragments
+            (loop (list frame) (frame-type frame)))
+           ((final-fragment-frame? frame) ; concatenate all fragments
+            (handle-data-frame type (frame-concatenate
+                                     (reverse (cons frame fragments))))
+            (loop '() #f))
+           ((fragment-frame? frame)     ; add a fragment
+            (loop (cons frame fragments) type))
+           ((data-frame? frame)         ; unfragmented data frame
+            (handle-data-frame (frame-type frame) (frame-data frame))
+            (loop '() #f)))))
+      (lambda (key . args)
+        (let ((message (format #f "~a: ~s" key args)))
+          ((.on-error websocket) websocket (format #f "read failed: ~s\n" websocket))
+          (if (websocket-socket-open? websocket) (loop '() #f)
+              (websocket-close websocket message)))))))
 
 ;; See Section 3 - WebSocket URIs
 (define (encrypted-websocket-scheme? uri)