Commitea578f14Recorded30 Jun 2026Repositorysigil-http
Thread connect-timeout: through the client; relock onto sigil-tls 0.16.2
Message
Add an opt-in connect-timeout: keyword (seconds) to http-request and the convenience/JSON wrappers, passed into connect-to-server which hands it to tls-connect (converted to ms) for HTTPS. Bounds the TCP connect phase so a blackholed address fails fast instead of hanging on the OS SYN timeout. Default #f keeps the blocking connect; plain HTTP is unchanged (socket layer has no connect-timeout knob yet).
Changed
package.sgl | 2 +-
sigil.lock | 4 ++--
src/sigil/http/client.sgl | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------
3 files changed, 71 insertions(+), 40 deletions(-)Diff
package.sglmodified
@@ -5,7 +5,7 @@
5
6
(package 7
name: "sigil-http"−8
version: "0.16.4"+8
version: "0.16.5" 9
sigil: "^0.16" 10
description: "HTTP client and server library for Sigil" 11
url: "https://codeberg.org/sigil/sigil-http"sigil.lockmodified
@@ -14,8 +14,8 @@
14
(package name: "sigil-tls" 15
url: "codeberg:sigil/sigil-tls" 16
ref: "^0.16.0"−17
sha: "407d9d63ccb6a75a88f39f0588bf012c0eed2d6e"−18
version: "0.16.1")+17
sha: "e67c3b00b19bc495e38527203d0a563a8bcd5eab"+18
version: "0.16.2") 19
(package name: "sigil-test" 20
url: "codeberg:sigil/sigil" 21
ref: "^0.17"src/sigil/http/client.sglmodified
@@ -101,8 +101,8 @@
101
(define %tls-set-non-blocking! (delay (tls-ref 'tls-set-non-blocking!))) 102
103
;; TLS function wrappers−104
(define (tls-connect* host port)−105
((force %tls-connect) host port))+104
(define (tls-connect* host port . connect-timeout-ms)+105
(apply (force %tls-connect) host port connect-timeout-ms)) 106
107
(define (tls-connection?* conn) 108
(and (force tls-module)@@ -220,14 +220,22 @@
220
;; Low-level Connection Helpers 221
;; ============================================================ 222
−223
;;; Connect to a server, using TLS if scheme is https−224
;;; Returns connection object or #f on failure−225
(define (connect-to-server parsed-url)+223
;;; Connect to a server, using TLS if scheme is https.+224
;;; Returns connection object or #f on failure.+225
;;;+226
;;; `connect-timeout` (seconds, or #f) bounds the TLS connect phase+227
;;; so a blackholed address can't hang on the OS SYN timeout. It is+228
;;; only honored for HTTPS (the TLS connect path); plain HTTP uses+229
;;; the socket layer's blocking connect (no timeout knob there yet).+230
(define (connect-to-server parsed-url connect-timeout) 231
(let ((host (url-host parsed-url)) 232
(port (url-port parsed-url)) 233
(scheme (url-scheme parsed-url))) 234
(if (string=? scheme "https")−230
(tls-connect* host port)+235
(if (and connect-timeout (> connect-timeout 0))+236
(tls-connect* host port+237
(exact (round (* connect-timeout 1000))))+238
(tls-connect* host port)) 239
(tcp-connect host port)))) 240
241
;;; Write data to connection (socket or TLS)@@ -707,6 +715,10 @@
715
;;; connection fails cleanly instead of hanging). When omitted (the 716
;;; default), reads block exactly as before. 717
;;;+718
;;; The optional `connect-timeout:` keyword (seconds) bounds the+719
;;; HTTPS connect phase, so a blackholed address can't hang on the+720
;;; OS SYN timeout. Omitted/#f keeps the blocking connect.+721
;;; 722
;;; ```scheme 723
;;; (http-request 'GET "https://api.example.com/users" 724
;;; headers: #{ authorization: "Bearer token" })@@ -716,11 +728,12 @@
728
;;; body: "{\"name\": \"Alice\"}" 729
;;; timeout: 25) 730
;;; ```−719
(define (http-request method url (keys: (headers #{}) (body #f) (timeout #f)))+731
(define (http-request method url (keys: (headers #{}) (body #f) (timeout #f)+732
(connect-timeout #f))) 733
(: symbol? string? (headers: dict?) (body: (maybe string?))−721
(timeout: (maybe number?)) -> any?)+734
(timeout: (maybe number?)) (connect-timeout: (maybe number?)) -> any?) 735
(let* ((parsed-url (parse-url url))−723
(conn (connect-to-server parsed-url)))+736
(conn (connect-to-server parsed-url connect-timeout))) 737
(if (not conn) 738
#f 739
(let ((deadline (timeout->deadline timeout))@@ -742,9 +755,11 @@
755
;;; (http-get "https://api.example.com/users" 756
;;; headers: #{ authorization: "Bearer token" }) 757
;;; ```−745
(define (http-get url (keys: (headers #{}) (timeout #f)))−746
(: string? (headers: dict?) (timeout: (maybe number?)) -> any?)−747
(http-request 'GET url headers: headers timeout: timeout))+758
(define (http-get url (keys: (headers #{}) (timeout #f) (connect-timeout #f)))+759
(: string? (headers: dict?) (timeout: (maybe number?))+760
(connect-timeout: (maybe number?)) -> any?)+761
(http-request 'GET url headers: headers timeout: timeout+762
connect-timeout: connect-timeout)) 763
764
;;; HTTP POST request. 765
;;;@@ -758,12 +773,14 @@
773
;;; "{\"key\": \"value\"}" 774
;;; headers: #{ content-type: "application/json" }) 775
;;; ```−761
(define (http-post url body (keys: (headers #{}) (timeout #f)))−762
(: string? any? (headers: dict?) (timeout: (maybe number?)) -> any?)+776
(define (http-post url body (keys: (headers #{}) (timeout #f) (connect-timeout #f)))+777
(: string? any? (headers: dict?) (timeout: (maybe number?))+778
(connect-timeout: (maybe number?)) -> any?) 779
(let ((hdrs (if (and (dict? headers) (not (dict-contains? headers content-type:))) 780
(dict-set headers content-type: "application/x-www-form-urlencoded") 781
headers)))−766
(http-request 'POST url headers: hdrs body: body timeout: timeout)))+782
(http-request 'POST url headers: hdrs body: body timeout: timeout+783
connect-timeout: connect-timeout))) 784
785
;;; HTTP PUT request. 786
;;;@@ -772,9 +789,11 @@
789
;;; "{\"name\": \"Alice\"}" 790
;;; headers: #{ content-type: "application/json" }) 791
;;; ```−775
(define (http-put url body (keys: (headers #{}) (timeout #f)))−776
(: string? any? (headers: dict?) (timeout: (maybe number?)) -> any?)−777
(http-request 'PUT url headers: headers body: body timeout: timeout))+792
(define (http-put url body (keys: (headers #{}) (timeout #f) (connect-timeout #f)))+793
(: string? any? (headers: dict?) (timeout: (maybe number?))+794
(connect-timeout: (maybe number?)) -> any?)+795
(http-request 'PUT url headers: headers body: body timeout: timeout+796
connect-timeout: connect-timeout)) 797
798
;;; HTTP DELETE request. 799
;;;@@ -784,9 +803,11 @@
803
;;; (http-delete "https://api.example.com/users/123" 804
;;; headers: #{ authorization: "Bearer token" }) 805
;;; ```−787
(define (http-delete url (keys: (headers #{}) (timeout #f)))−788
(: string? (headers: dict?) (timeout: (maybe number?)) -> any?)−789
(http-request 'DELETE url headers: headers timeout: timeout))+806
(define (http-delete url (keys: (headers #{}) (timeout #f) (connect-timeout #f)))+807
(: string? (headers: dict?) (timeout: (maybe number?))+808
(connect-timeout: (maybe number?)) -> any?)+809
(http-request 'DELETE url headers: headers timeout: timeout+810
connect-timeout: connect-timeout)) 811
812
;;; HTTP HEAD request. 813
;;;@@ -797,9 +818,11 @@
818
;;; (let ((res (http-head "https://example.com/file.pdf"))) 819
;;; (http-response-header res "Content-Length")) 820
;;; ```−800
(define (http-head url (keys: (headers #{}) (timeout #f)))−801
(: string? (headers: dict?) (timeout: (maybe number?)) -> any?)−802
(http-request 'HEAD url headers: headers timeout: timeout))+821
(define (http-head url (keys: (headers #{}) (timeout #f) (connect-timeout #f)))+822
(: string? (headers: dict?) (timeout: (maybe number?))+823
(connect-timeout: (maybe number?)) -> any?)+824
(http-request 'HEAD url headers: headers timeout: timeout+825
connect-timeout: connect-timeout)) 826
827
;;; HTTP OPTIONS request. 828
;;;@@ -810,9 +833,11 @@
833
;;; (http-response-header res "Allow")) 834
;;; ; => "GET, POST, OPTIONS" 835
;;; ```−813
(define (http-options url (keys: (headers #{}) (timeout #f)))−814
(: string? (headers: dict?) (timeout: (maybe number?)) -> any?)−815
(http-request 'OPTIONS url headers: headers timeout: timeout))+836
(define (http-options url (keys: (headers #{}) (timeout #f) (connect-timeout #f)))+837
(: string? (headers: dict?) (timeout: (maybe number?))+838
(connect-timeout: (maybe number?)) -> any?)+839
(http-request 'OPTIONS url headers: headers timeout: timeout+840
connect-timeout: connect-timeout)) 841
842
;;; HTTP PATCH request. 843
;;;@@ -824,9 +849,11 @@
849
;;; "{\"email\": \"[email protected]\"}" 850
;;; headers: #{ content-type: "application/json" }) 851
;;; ```−827
(define (http-patch url body (keys: (headers #{}) (timeout #f)))−828
(: string? any? (headers: dict?) (timeout: (maybe number?)) -> any?)−829
(http-request 'PATCH url headers: headers body: body timeout: timeout))+852
(define (http-patch url body (keys: (headers #{}) (timeout #f) (connect-timeout #f)))+853
(: string? any? (headers: dict?) (timeout: (maybe number?))+854
(connect-timeout: (maybe number?)) -> any?)+855
(http-request 'PATCH url headers: headers body: body timeout: timeout+856
connect-timeout: connect-timeout)) 857
858
;; ============================================================ 859
;; JSON Conveniences@@ -862,9 +889,11 @@
889
;;; (http-get/json "https://api.example.com/users" 890
;;; headers: #{ authorization: "Bearer token" }) 891
;;; ```−865
(define (http-get/json url (keys: (headers #{}) (timeout #f)))−866
(: string? (headers: dict?) (timeout: (maybe number?)) -> any?)−867
(let ((res (http-get url headers: headers timeout: timeout)))+892
(define (http-get/json url (keys: (headers #{}) (timeout #f) (connect-timeout #f)))+893
(: string? (headers: dict?) (timeout: (maybe number?))+894
(connect-timeout: (maybe number?)) -> any?)+895
(let ((res (http-get url headers: headers timeout: timeout+896
connect-timeout: connect-timeout))) 897
(and res (http-response-json res)))) 898
899
;;; HTTP POST request with JSON body, expecting JSON response.@@ -882,11 +911,13 @@
911
;;; #{ name: "Alice" } 912
;;; headers: #{ authorization: "Bearer token" }) 913
;;; ```−885
(define (http-post/json url body (keys: (headers #{}) (timeout #f)))−886
(: string? any? (headers: dict?) (timeout: (maybe number?)) -> any?)+914
(define (http-post/json url body (keys: (headers #{}) (timeout #f) (connect-timeout #f)))+915
(: string? any? (headers: dict?) (timeout: (maybe number?))+916
(connect-timeout: (maybe number?)) -> any?) 917
(let* ((json-body (json-encode* body)) 918
(hdrs (dict-set headers content-type: "application/json"))−889
(res (http-request 'POST url headers: hdrs body: json-body timeout: timeout)))+919
(res (http-request 'POST url headers: hdrs body: json-body timeout: timeout+920
connect-timeout: connect-timeout))) 921
(and res (http-response-json res)))) 922
923
;; ============================================================@@ -1001,7 +1032,7 @@
1032
(on-progress #f) 1033
(max-redirects 5))) 1034
(let* ((parsed-url (parse-url url))−1004
(conn (connect-to-server parsed-url)))+1035
(conn (connect-to-server parsed-url #f))) 1036
(if (not conn) 1037
#f 1038
(let ((request-str (build-request-string 'GET parsed-url headers #f)))