(use-modules (tests) (fibers) (srfi srfi-71) (ice-9 rdelim) (ice-9 exceptions) (unit-test) (web uri) (web client) (web request) (web response) (knots resource-pool) (knots web-server) (knots web)) ;; Test that call-with-cached-connection passes the port to proc and ;; returns its result. (run-fibers-for-tests (lambda () (let* ((port (open-input-string "")) (cache (make-fixed-size-resource-pool (list port)))) (assert-equal 'ok (call-with-cached-connection cache (lambda (p) 'ok))) (destroy-resource-pool cache)))) ;; Test that call-with-cached-connection retries when the checked-out ;; port is already closed, using a fresh connection from the pool. (run-fibers-for-tests (lambda () (let* ((n 0) (cache (make-resource-pool (lambda () (set! n (+ n 1)) (if (= n 1) (let ((p (open-input-string ""))) (close-port p) p) (open-input-string ""))) 1 ;; Without a destructor, the resource pool calls (#f port) ;; when destroying the closed-port resource, looping forever. #:destructor (const #t)))) (assert-equal 'ok (call-with-cached-connection cache (lambda (p) 'ok))) (destroy-resource-pool cache)))) ;; Test that call-with-connection-cache provides a working cache and ;; destroys it after the body returns. (run-fibers-for-tests (lambda () (let* ((web-server (run-knots-web-server (lambda (request) (values '((content-type . (text/plain))) "ok")) #:port 0)) (server-port (web-server-port web-server)) (uri (build-uri 'http #:host "127.0.0.1" #:port server-port))) (assert-equal 200 (call-with-connection-cache uri 1 (lambda (cache) (call-with-cached-connection cache (lambda (p) (let ((response body (http-get uri #:port p #:keep-alive? #t))) (response-code response)))))))))) ;; Test that http-fold-requests sends requests and folds over responses. ;; The proc must drain the body port between responses so that HTTP ;; pipelining works correctly. (run-fibers-for-tests (lambda () (let* ((web-server (run-knots-web-server (lambda (request) (values '((content-type . (text/plain))) "ok")) #:port 0)) (server-port (web-server-port web-server)) (uri (build-uri 'http #:host "127.0.0.1" #:port server-port)) (cache (make-connection-cache uri 1)) (requests (list (build-request uri) (build-request uri)))) (let ((codes (http-fold-requests cache (lambda (req resp body result) (read-string body) ; drain body before next pipelined response (cons (response-code resp) result)) '() requests))) (assert-equal '(200 200) codes)) (destroy-resource-pool cache)))) ;; Test that http-fold-requests reconnects and retries remaining requests when ;; the server closes the connection mid-batch via Connection: close. Three ;; requests are sent in one batch; the server closes after the first response, ;; so the remaining two must be retried on a fresh connection. (run-fibers-for-tests (lambda () (let* ((n 0) (web-server (run-knots-web-server (lambda (request) (set! n (1+ n)) (if (= n 1) (values '((content-type . (text/plain)) (connection . (close))) "ok") (values '((content-type . (text/plain))) "ok"))) #:port 0)) (server-port (web-server-port web-server)) (uri (build-uri 'http #:host "127.0.0.1" #:port server-port)) (cache (make-connection-cache uri 1)) (requests (list (build-request uri) (build-request uri) (build-request uri)))) (let ((codes (http-fold-requests cache (lambda (req resp body result) (read-string body) (cons (response-code resp) result)) '() requests))) (assert-equal '(200 200 200) codes)) (destroy-resource-pool cache)))) ;; Test that write errors in send-batch are handled gracefully. Each request ;; carries a large header so that the batch data exceeds the TCP send buffer, ;; causing write-request to fail while the server has already closed the ;; connection after the first response. (run-fibers-for-tests (lambda () (let* ((n 0) (web-server (run-knots-web-server (lambda (request) (set! n (1+ n)) (if (= n 1) (values '((content-type . (text/plain)) (connection . (close))) "ok") (values '((content-type . (text/plain))) "ok"))) #:port 0)) (server-port (web-server-port web-server)) (uri (build-uri 'http #:host "127.0.0.1" #:port server-port)) (cache (make-connection-cache uri 1)) (n-requests 100) ;; 100 requests x ~100 KB of headers each = ~10 MB, well above ;; the typical TCP send buffer, so writes fail mid-batch. (large-request (build-request uri #:headers `((x-padding . ,(make-string 100000 #\a))))) (requests (make-list n-requests large-request))) (let ((codes (http-fold-requests cache (lambda (req resp body result) (read-string body) (cons (response-code resp) result)) '() requests))) (assert-equal (make-list n-requests 200) codes)) (destroy-resource-pool cache)))) ;; Test that http-fold-requests processes multiple batches. With batch-size 2 ;; and 5 requests, three batches are needed; without the pending fix only the ;; first batch would be processed. (run-fibers-for-tests (lambda () (let* ((web-server (run-knots-web-server (lambda (request) (values '((content-type . (text/plain))) "ok")) #:port 0)) (server-port (web-server-port web-server)) (uri (build-uri 'http #:host "127.0.0.1" #:port server-port)) (cache (make-connection-cache uri 1)) (requests (make-list 5 (build-request uri)))) (let ((codes (http-fold-requests cache (lambda (req resp body result) (read-string body) (cons (response-code resp) result)) '() requests #:batch-size 2))) (assert-equal (make-list 5 200) codes)) (destroy-resource-pool cache)))) ;; Test that an exception raised by proc propagates out of http-fold-requests. (run-fibers-for-tests (lambda () (let* ((web-server (run-knots-web-server (lambda (request) (values '((content-type . (text/plain))) "ok")) #:port 0)) (server-port (web-server-port web-server)) (uri (build-uri 'http #:host "127.0.0.1" #:port server-port)) (cache (make-connection-cache uri 1)) (requests (list (build-request uri)))) (assert-equal 'proc-exception (exception-message (with-exception-handler (lambda (exn) exn) (lambda () (http-fold-requests cache (lambda (req resp body result) (raise-exception (make-exception-with-message 'proc-exception))) '() requests)) #:unwind? #t))) (destroy-resource-pool cache)))) (display "web test finished successfully\n")