AtlatestRepositorysigil-http
sigil-http / tree / test / integrationstreaming-server-main.sgl
1
;;; (t1-streaming-server main) - integration test server for the T1 fix.2
;;;3
;;; Exercises the streaming response paths (`http-response/file` and4
;;; `http-response/sse`), which spawn a `go` goroutine and therefore require a5
;;; live async scheduler. The point of T1 is that these now work from a BARE6
;;; `http-serve` (no surrounding `with-async`).7
;;;8
;;; Usage: t1-streaming-server <port> <asset-path> [bare|wrapped]9
;;;10
;;; bare -> (http-serve handler ...) with NO with-async (default). This is11
;;; the case the T1 fix makes work: http-serve self-installs a12
;;; scheduler so the streaming `go` succeeds.13
;;; wrapped -> (with-async (go (http-server-start server))) — the existing14
;;; consumer pattern (mirrors live-crafter). Must keep working15
;;; unchanged (no nested/duplicate scheduler).17
(define-library (t1-streaming-server main)18
(import (sigil core)19
(sigil io)20
(sigil process)21
(sigil async)22
(sigil http server)23
(sigil http request)24
(sigil http response))26
(export main)28
(begin30
;; Asset path captured from argv at startup.31
(define *asset-path* (make-parameter "/dev/null"))33
;; A compressible text body comfortably over the gzip size floor (1 KB).34
(define big-text35
(let loop ((n 0) (acc ""))36
(if (>= n 200)37
acc38
(loop (+ n 1)39
(string-append acc "The quick brown fox jumps over the lazy dog. ")))))41
(define (handler request)42
(let ((path (http-request-path request)))43
(cond44
((string=? path "/hello")45
(http-response/text HTTP-OK "Hello, World!"))46
;; Large compressible text — exercises opt-in gzip (T11).47
((string=? path "/big-text")48
(http-response/text HTTP-OK big-text))49
;; Streaming file — the "image sometimes doesn't download" path.50
((string=? path "/file")51
(http-response/file (*asset-path*)))52
;; Server-Sent Events — five events then close.53
((string=? path "/sse")54
(http-response/sse55
(lambda (send close)56
(let loop ((i 1))57
(if (<= i 5)58
(begin59
(send "tick" (number->string i))60
(loop (+ i 1)))61
(close))))))62
(else (http-response/not-found)))))64
;;; Modes:65
;;; bare -> (http-serve ...) no with-async (T1 fix case; default)66
;;; wrapped -> (with-async (go (http-server-start ...))) consumer pattern67
;;; gzip -> bare + gzip: #t (opt-in gzip; T11)68
(define (main)69
(let* ((args (cdr (command-line)))70
(port (string->number (list-ref args 0)))71
(asset (list-ref args 1))72
(mode (if (>= (length args) 3) (list-ref args 2) "bare")))73
(*asset-path* asset)74
(cond75
((string=? mode "wrapped")76
;; Existing-consumer regression path: the server runs as a77
;; goroutine inside a caller-owned scheduler. http-server-start78
;; must reuse that scheduler rather than nest a second one.79
(let ((server (make-http-server handler port: port host: "127.0.0.1")))80
(with-async (go (http-server-start server)))))81
((string=? mode "gzip")82
;; Opt-in gzip enabled (T11). Still a bare call (T1 fix applies).83
(http-serve handler port: port host: "127.0.0.1" gzip: #t))84
(else85
;; Bare path: no with-async at the call site (the T1 fix case).86
(http-serve handler port: port host: "127.0.0.1")))))))