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>
Thu, 18 Mar 2021 21:51:47 +0000 (22:51 +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 a6373845fcd7eb44a85f362c85c67adf208f62e5..49866726ae6c802e1ebbb8535b995bb0fb08e833 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          ;expect: wrong-type-arg (open port), system-error
+      (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)
+        (unless (and
+                 (memq key '(system-error wrong-type-arg))
+                 (match args
+                   (((or "get-u8" "get-bytevector-n" "lookahead-u8") arg ...)
+                    #t)
+                   (_ #f)))
+          (apply throw 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)