Skip to content

Commit c8a539a

Browse files
committed
session: add support for middleware
1 parent ce8ed02 commit c8a539a

File tree

7 files changed

+304
-40
lines changed

7 files changed

+304
-40
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#lang racket/base
2+
3+
(require "private/middleware.rkt")
4+
(provide (all-from-out "private/middleware.rkt"))
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#lang racket/base
2+
3+
(require net/url
4+
racket/contract/base
5+
"contract.rkt"
6+
"response.rkt")
7+
8+
(provide
9+
middleware/c
10+
middleware-continuation/c
11+
(contract-out
12+
[compose-middleware
13+
(case->
14+
(-> middleware/c middleware/c)
15+
(-> middleware/c middleware/c middleware/c)
16+
(-> middleware/c middleware/c #:rest middleware/c middleware/c))]))
17+
18+
(define middleware-continuation/c
19+
(-> url?
20+
#:method method/c
21+
#:headers headers/c
22+
#:params query-params/c
23+
#:auth (or/c #f auth-procedure/c)
24+
#:data (or/c #f bytes? string? input-port? payload-procedure/c)
25+
#:history (listof response?)
26+
#:attempts exact-nonnegative-integer?
27+
#:redirects exact-nonnegative-integer?
28+
response?))
29+
30+
(define middleware/c
31+
(-> url?
32+
middleware-continuation/c
33+
#:method method/c
34+
#:headers headers/c
35+
#:params query-params/c
36+
#:auth (or/c #f auth-procedure/c)
37+
#:data (or/c #f bytes? string? input-port? payload-procedure/c)
38+
#:history (listof response?)
39+
#:attempts exact-nonnegative-integer?
40+
#:redirects exact-nonnegative-integer?
41+
response?))
42+
43+
(define compose-middleware
44+
(case-lambda
45+
[(a) a]
46+
[(a b)
47+
(make-keyword-procedure
48+
(lambda (kws kw-args u go . args)
49+
(keyword-apply
50+
a kws kw-args u
51+
(make-keyword-procedure
52+
(lambda (kws kw-args u . args) ;; noqa
53+
(keyword-apply b kws kw-args u go args)))
54+
args)))]
55+
[(a b . args)
56+
(apply compose-middleware (compose-middleware a b) args)]))

http-easy-lib/http-easy/private/session.rkt

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"contract.rkt"
1515
"error.rkt"
1616
"logger.rkt"
17+
"middleware.rkt"
1718
"payload.rkt"
1819
"pool.rkt"
1920
"proxy.rkt"
@@ -36,7 +37,8 @@
3637
[#:pool-config pool-config?
3738
#:ssl-context (or/c #f ssl-client-context? (promise/c ssl-client-context?))
3839
#:cookie-jar (or/c #f (is-a?/c cookie-jar<%>))
39-
#:proxies (listof proxy?)]
40+
#:proxies (listof proxy?)
41+
#:middleware (or/c #f middleware/c)]
4042
session?)]
4143
[session?
4244
(-> any/c boolean?)]
@@ -75,14 +77,16 @@
7577
ssl-ctx
7678
cookies
7779
proxies
80+
middleware
7881
[closed? #:mutable])
7982
#:transparent)
8083

8184
(define (make-session
8285
#:pool-config [conf (make-pool-config)]
8386
#:ssl-context [ssl-ctx (delay/sync (ssl-secure-client-context))]
8487
#:cookie-jar [cookies #f]
85-
#:proxies [proxies null])
88+
#:proxies [proxies null]
89+
#:middleware [middleware #f])
8690
(define the-session
8791
(session
8892
#;cust (make-custodian)
@@ -92,6 +96,7 @@
9296
#;ssql-ctx ssl-ctx
9397
#;cookies cookies
9498
#;proxies proxies
99+
#;middleware middleware
95100
#;closed? #f))
96101
(will-register executor the-session session-close!)
97102
(log-http-easy-debug "session opened")
@@ -170,15 +175,16 @@
170175
[(supplied? json) (json-payload json)]
171176
[else data]))
172177

173-
(define (go u
174-
#:method [method method] ;; noqa
175-
#:headers [headers headers] ;; noqa
176-
#:params [params params] ;; noqa
177-
#:auth [auth auth] ;; noqa
178-
#:data [data the-data] ;; noqa
179-
#:history [history null]
180-
#:attempts [attempts-remaining max-attempts]
181-
#:redirects [redirects-remaining max-redirects])
178+
(define (go
179+
#:method method ;; noqa
180+
#:headers headers ;; noqa
181+
#:params params ;; noqa
182+
#:auth auth ;; noqa
183+
#:data data ;; noqa
184+
#:history history
185+
#:attempts attempts-remaining
186+
#:redirects redirects-remaining
187+
u)
182188
(let*-values ([(headers) (hash-set headers 'user-agent user-agent)]
183189
[(headers) (maybe-add-cookie-header sess u headers)]
184190
[(headers params)
@@ -209,7 +215,16 @@
209215
[(positive? attempts-remaining)
210216
(log-http-easy-debug "retrying~n attempts remaining: ~a" (sub1 attempts-remaining))
211217
(parameterize-break enable-breaks?
212-
(go u #:attempts (sub1 attempts-remaining) #:history history))]
218+
(go+middleware
219+
u
220+
#:method method
221+
#:headers headers
222+
#:params params
223+
#:auth auth
224+
#:data data
225+
#:history history
226+
#:attempts (sub1 attempts-remaining)
227+
#:redirects redirects-remaining))]
213228
[else
214229
(log-http-easy-warning "out of retries; bubbling up exception")
215230
(raise e)]))])
@@ -280,14 +295,18 @@
280295
(response-drain! resp (timeout-config-request timeouts))
281296
(response-close! resp)
282297
(parameterize-break enable-breaks?
283-
(go dest-url
284-
#:method (case (response-status-code resp)
285-
[(301 302 303) 'get]
286-
[(307 308) method])
287-
#:headers (hash-remove headers 'authorization)
288-
#:auth (and (same-origin? dest-url u) auth)
289-
#:history (cons resp history)
290-
#:redirects (sub1 redirects-remaining)))]
298+
(go+middleware
299+
dest-url
300+
#:method (case (response-status-code resp)
301+
[(301 302 303) 'get]
302+
[(307 308) method])
303+
#:headers (hash-remove headers 'authorization)
304+
#:params params
305+
#:auth (and (same-origin? dest-url u) auth)
306+
#:data data
307+
#:history (cons resp history)
308+
#:attempts attempts-remaining
309+
#:redirects (sub1 redirects-remaining)))]
291310
[(or close? (not stream?))
292311
(response-drain! resp (timeout-config-request timeouts))
293312
(response-close! resp)
@@ -298,7 +317,25 @@
298317
(will-register executor resp response-close!)
299318
resp])))))
300319

301-
(go (->url urlish)))
320+
(define go+middleware
321+
(cond
322+
[(session-middleware sess)
323+
=> (lambda (m)
324+
(make-keyword-procedure
325+
(lambda (kws kw-args u . args)
326+
(keyword-apply m kws kw-args u go args))))]
327+
[else go]))
328+
329+
(go+middleware
330+
(->url urlish)
331+
#:method method
332+
#:headers headers
333+
#:params params
334+
#:auth auth
335+
#:data the-data
336+
#:history null
337+
#:attempts max-attempts
338+
#:redirects max-redirects))
302339

303340
;; https://www.rfc-editor.org/rfc/rfc2616#section-14.30
304341
(define (ensure-absolute-url orig location)

http-easy-lib/info.rkt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#lang info
22

33
(define license 'BSD-3-Clause)
4-
(define version "0.10")
4+
(define version "0.11")
55
(define collection "net")
66
(define deps
77
'(["base" #:version "8.1.0.4"]

http-easy-test/info.rkt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"plot-gui-lib"
1313
"plot-lib"
1414
"rackunit-lib"
15-
("resource-pool-lib" #:version "0.1")
15+
["resource-pool-lib" #:version "0.1"]
1616
"threading-lib"
1717
"web-server-lib"))
18-
(define update-implies '("http-easy"))
18+
(define update-implies
19+
'("http-easy"))

http-easy-test/net/http-easy/http-easy.rkt

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
(require json
44
net/cookies
55
net/http-easy
6+
net/http-easy/middleware
67
net/url
78
racket/class
89
racket/match
@@ -27,6 +28,8 @@
2728
request-bindings/raw
2829
request-headers/raw
2930
request-post-data/raw
31+
response/empty
32+
response/jsexpr
3033
response/output
3134
see-other
3235
temporarily/same-method)
@@ -570,7 +573,89 @@
570573
(check-exn
571574
#rx"session-lease: session closed"
572575
(lambda ()
573-
(session-request s "https://example.com"))))))))
576+
(session-request s "https://example.com")))))
577+
578+
(let* ([timings (make-hash)]
579+
[timing-middleware
580+
(make-keyword-procedure
581+
(lambda (kws kw-args u k)
582+
(define-values (res cpu-time real-time gc-time)
583+
(time-apply
584+
(lambda ()
585+
(keyword-apply k kws kw-args u null))
586+
null))
587+
(hash-update!
588+
#;ht timings
589+
#;key u
590+
#;update-proc
591+
(lambda (timings)
592+
(cons (list cpu-time real-time gc-time) timings))
593+
#;failure-result null)
594+
(car res)))]
595+
[token-box (box "token-1")]
596+
[oauth-middleware
597+
(make-keyword-procedure
598+
(lambda (kws kw-args u k)
599+
(match (keyword-apply k kws kw-args u null)
600+
[(response #:status-code 401)
601+
(set-box! token-box "token-2")
602+
(keyword-apply k kws kw-args u null)]
603+
[resp resp])))]
604+
[oauth-handler
605+
(lambda (req)
606+
(define heads
607+
(request-headers/raw req))
608+
(define token
609+
(let* ([v (headers-assq* #"authorization" heads)]
610+
[v (header-value v)])
611+
(bytes->string/utf-8 v #f 7)))
612+
(match token
613+
["token-1"
614+
(response/jsexpr
615+
#:code 401
616+
(hasheq 'error "token expired"))]
617+
["token-2"
618+
(response/jsexpr
619+
(hasheq 'message "ok"))]
620+
["token-3"
621+
(response/jsexpr
622+
#:code 401
623+
(hasheq 'error "invalid token"))]))])
624+
(test-suite
625+
"middleware"
626+
627+
(test-case "instrumentation"
628+
(call-with-web-server
629+
(lambda (_req)
630+
(response/empty))
631+
(lambda (addr)
632+
(parameterize ([current-session (make-session #:middleware timing-middleware)])
633+
(define res (get addr))
634+
(check-equal? (response-status-code res) 204)
635+
(check-not-false (hash-ref timings (string->url addr)))))))
636+
637+
(test-case "oauth refresh"
638+
(call-with-web-server
639+
oauth-handler
640+
(lambda (addr)
641+
(set-box! token-box "token-1")
642+
(define (oauth url headers params)
643+
((bearer-auth (unbox token-box)) url headers params))
644+
(parameterize ([current-session (make-session #:middleware oauth-middleware)])
645+
(check-equal? (response-status-code (get #:auth oauth addr)) 200)))))
646+
647+
(test-case "composition"
648+
(call-with-web-server
649+
oauth-handler
650+
(lambda (addr)
651+
(set-box! token-box "token-1")
652+
(define (oauth url headers params)
653+
((bearer-auth (unbox token-box)) url headers params))
654+
(define composed-middleware
655+
(compose-middleware oauth-middleware timing-middleware))
656+
(parameterize ([current-session (make-session #:middleware composed-middleware)])
657+
(check-equal? (response-status-code (get #:auth oauth addr)) 200)
658+
(check-= (length (hash-ref timings (string->url addr))) 2 0))))))))))
574659

575660
(module+ test
576661
(require rackunit/text-ui)

0 commit comments

Comments
 (0)