websocket: Support for sending fragmented frames.
authorJan (janneke) Nieuwenhuizen <janneke@gnu.org>
Mon, 22 Apr 2019 17:30:22 +0000 (19:30 +0200)
committerJan (janneke) Nieuwenhuizen <janneke@gnu.org>
Thu, 18 Mar 2021 21:51:46 +0000 (22:51 +0100)
* 8sync/systems/websocket/frame.scm (make-text-frame): Add keyword
parameters final and continuation.
(make-binary-frame): Likewise.
* 8sync/systems/websocket/client.scm (make-fragmented-frames): New
function.
(websocket-send): Use it to send fragmented frames.

8sync/systems/websocket/client.scm
8sync/systems/websocket/frame.scm

index 186b1b29033454fef0e5b1d056f15188692ec926..9f2b0f06807a1494e51e31c5c27113543885a6a6 100644 (file)
             ((.on-error websocket) websocket (format #f "not a websocket uri: ~s" uri-or-string))))
       ((.on-error websocket) websocket (format #f "cannot open websocket in state: ~s" (.state websocket)))))
 
+(define (subbytevector bv start end)
+  (if (= (bytevector-length bv) end) bv
+      (let* ((length (- end start))
+             (sub (make-bytevector length)))
+        (bytevector-copy! bv start sub 0 length)
+        sub)))
+
+(define* (make-fragmented-frames data #:key (fragment-size (expt 2 15)))
+  (let ((length (if (string? data) (string-length data)
+                    (bytevector-length data))))
+    (let loop ((offset 0))
+      (let* ((size (min fragment-size (- length offset)))
+             (end (+ offset size))
+             (final? (= end length))
+             (continuation? (not (zero? offset)))
+             (frame (if (string? data) (make-text-frame (substring data offset end) #:final? final? #:continuation? continuation?)
+                        (make-binary-frame (subbytevector data offset end) #:final? final? #:continuation? continuation?))))
+        (if final? (list frame)
+            (cons frame (loop end)))))))
+
 (define-method (websocket-send (websocket <websocket>) message data)
   (catch #t           ; expect: wrong-type-arg (open port), system-error
     (lambda _
-      (write-frame
-       (cond ((string? data)
-              (make-text-frame data))
-             ((bytevector? data)
-              (make-binary-frame data)))
-       (.socket websocket)))
+      (let* ((frames (make-fragmented-frames data)))
+        (let loop ((frames frames) (written '(nothing)))
+          (when (pair? frames)
+            (write-frame (car frames) (.socket websocket))
+            (loop (cdr frames) (cons (car frames) written))))))
     (lambda (key . args)
       (unless (and (memq key '(system-error wrong-type-arg))
                    (match args
index 831456a61cccb0580d9480cb7734f4be69ca2b89..35dc5511aec13da172bdb637be1337b6f466b20b 100644 (file)
@@ -106,16 +106,16 @@ bytevector BV, masked with MASKING-KEY.  By default, the data is
 unmasked."
   (make-frame #t 'close masking-key bv))
 
-(define* (make-text-frame text #:optional masking-key)
+(define* (make-text-frame text #:optional masking-key #:key (final? #t) (continuation? #f)) ;; bah: optional
   "Return a text data frame containing the string TEXT, masked with MASKING-KEY.
 By default, the text is unmasked."
-  (make-frame #t 'text masking-key (string->utf8 text)))
+  (make-frame final? (if continuation? 'continuation 'text) masking-key (string->utf8 text)))
 
-(define* (make-binary-frame bv #:optional masking-key)
+(define* (make-binary-frame bv #:optional masking-key #:key (final? #t) (continuation? #f))
   "Return a binary data frame containing the contents of the
 bytevector BV, masked with MASKING-KEY.  By default, the data is
 unmasked."
-  (make-frame #t 'binary masking-key bv))
+  (make-frame final? (if continuation? 'continuation 'binary) masking-key bv))
 
 (define (continuation-frame? frame)
   "Return #t if FRAME is a continuation frame."