2016-10-29 11:16:29 +00:00
|
|
|
#lang racket/base
|
|
|
|
|
2016-11-20 22:51:38 +00:00
|
|
|
(provide random-hex-string
|
2016-10-31 00:31:29 +00:00
|
|
|
extend-url-string-query
|
|
|
|
web-respond/status!
|
2016-11-01 02:29:06 +00:00
|
|
|
web-request!
|
2016-11-21 21:04:07 +00:00
|
|
|
web-post/form-parameters!
|
|
|
|
analyze-response
|
2016-11-08 17:20:19 +00:00
|
|
|
parse-link-headers
|
2016-11-20 21:13:40 +00:00
|
|
|
link-header-ref
|
|
|
|
maybe-link-header
|
|
|
|
vh
|
|
|
|
canonical-url)
|
2016-10-29 11:16:29 +00:00
|
|
|
|
2016-10-31 21:49:06 +00:00
|
|
|
(require racket/dict)
|
2016-11-01 02:29:06 +00:00
|
|
|
(require racket/match)
|
|
|
|
(require racket/string)
|
2016-10-29 11:16:29 +00:00
|
|
|
(require (only-in file/sha1 bytes->hex-string))
|
|
|
|
(require (only-in racket/random crypto-random-bytes))
|
|
|
|
(require net/url)
|
2016-11-21 21:04:07 +00:00
|
|
|
(require net/uri-codec)
|
2016-10-31 00:31:29 +00:00
|
|
|
(require syndicate/actor)
|
|
|
|
(require syndicate/drivers/timer)
|
|
|
|
(require syndicate/drivers/web)
|
2016-10-29 11:16:29 +00:00
|
|
|
|
|
|
|
(module+ test (require rackunit))
|
|
|
|
|
|
|
|
(define (random-hex-string half-length)
|
|
|
|
(bytes->hex-string (crypto-random-bytes half-length)))
|
|
|
|
|
|
|
|
(define (extend-url-string-query urlstr extension)
|
|
|
|
(define u (string->url urlstr))
|
|
|
|
(url->string (struct-copy url u [query (append (url-query u) extension)])))
|
|
|
|
|
2016-11-01 02:29:06 +00:00
|
|
|
(module+ test
|
|
|
|
(check-equal? (extend-url-string-query "http://localhost/" '((a . "hi")))
|
|
|
|
"http://localhost/?a=hi")
|
|
|
|
(check-equal? (extend-url-string-query "http://localhost/" '())
|
|
|
|
"http://localhost/")
|
|
|
|
(check-equal? (extend-url-string-query "http://localhost?foo=bar" '())
|
|
|
|
"http://localhost?foo=bar")
|
|
|
|
(check-equal? (extend-url-string-query "http://localhost/?foo=bar" '())
|
|
|
|
"http://localhost/?foo=bar")
|
|
|
|
(check-equal? (extend-url-string-query "http://localhost?foo=bar" '((a . "hi")))
|
|
|
|
"http://localhost?foo=bar&a=hi")
|
|
|
|
(check-equal? (extend-url-string-query "http://localhost/?foo=bar" '((a . "hi")))
|
|
|
|
"http://localhost/?foo=bar&a=hi"))
|
|
|
|
|
2016-10-31 00:31:29 +00:00
|
|
|
(define (web-respond/status! id code message [body #""])
|
|
|
|
(web-respond/bytes! id
|
|
|
|
#:header (web-response-header
|
|
|
|
#:code code
|
|
|
|
#:message message
|
|
|
|
#:headers '())
|
|
|
|
body))
|
|
|
|
|
2016-10-31 21:49:06 +00:00
|
|
|
(define (web-request! verb urlstr
|
|
|
|
#:body [request-body #""]
|
|
|
|
#:headers [headers '()]
|
|
|
|
#:redirect-budget [redirect-budget 0])
|
2016-10-31 00:31:29 +00:00
|
|
|
(define req-id (gensym 'req))
|
|
|
|
(define u (string->url urlstr))
|
|
|
|
(define res (url->resource u))
|
2016-10-31 21:49:06 +00:00
|
|
|
(define-values (resp response-body)
|
|
|
|
(react/suspend (k)
|
|
|
|
(on-start
|
2016-11-21 21:15:01 +00:00
|
|
|
(log-info "~a --> ~a ~a ~v" req-id verb urlstr request-body)
|
2016-10-31 21:49:06 +00:00
|
|
|
(send! (web-request req-id
|
|
|
|
'outbound
|
|
|
|
(web-request-header verb res headers (url-query u))
|
|
|
|
request-body)))
|
|
|
|
(stop-when (message (web-response-complete req-id $resp $response-body))
|
2016-11-21 21:15:01 +00:00
|
|
|
(log-info "~a <-- ~v ~v" req-id resp response-body)
|
2016-10-31 21:49:06 +00:00
|
|
|
(k resp response-body))))
|
2016-11-01 02:29:32 +00:00
|
|
|
(define location (and resp (dict-ref (web-response-header-headers resp) 'location #f)))
|
|
|
|
(if (and resp
|
|
|
|
(eq? (web-response-header-code-type resp) 'redirection)
|
2016-10-31 21:49:06 +00:00
|
|
|
(positive? redirect-budget)
|
|
|
|
location)
|
|
|
|
(web-request! verb location
|
|
|
|
#:body request-body
|
|
|
|
#:headers headers
|
|
|
|
#:redirect-budget (- redirect-budget 1))
|
|
|
|
(values resp response-body)))
|
2016-10-31 00:31:29 +00:00
|
|
|
|
2016-11-21 21:04:07 +00:00
|
|
|
(define (web-post/form-parameters! urlstr query-alist
|
|
|
|
#:headers [headers '()]
|
|
|
|
#:redirect-budget [redirect-budget 0])
|
|
|
|
(web-request! 'post urlstr
|
|
|
|
#:headers (cons (cons 'content-type "application/x-www-form-urlencoded") headers)
|
|
|
|
#:body (string->bytes/utf-8 (alist->form-urlencoded query-alist))))
|
|
|
|
|
|
|
|
(define (analyze-response req-thunk k-successful k-unsuccessful)
|
|
|
|
(define-values (resp response-body) (req-thunk))
|
|
|
|
(if (web-response-successful? resp)
|
|
|
|
(k-successful)
|
|
|
|
(k-unsuccessful resp response-body)))
|
|
|
|
|
2016-11-01 02:29:06 +00:00
|
|
|
(define (parse-link-headers headers)
|
|
|
|
(define rel-dict-reverse-order
|
|
|
|
(for/fold [(seed (hasheq))] [(header headers)]
|
|
|
|
(if (eq? (car header) 'link)
|
|
|
|
(parse-link-header-value (cdr header) seed)
|
|
|
|
seed)))
|
|
|
|
(for/hasheq [((rel urls-rev) (in-hash rel-dict-reverse-order))]
|
|
|
|
(values rel (reverse urls-rev))))
|
|
|
|
|
2016-11-08 17:20:19 +00:00
|
|
|
(define (link-header-ref parsed-link-headers rel default)
|
|
|
|
(match (hash-ref parsed-link-headers rel '())
|
|
|
|
[(cons v _) v]
|
|
|
|
['() default]))
|
|
|
|
|
2016-11-01 02:29:06 +00:00
|
|
|
(define (parse-link-header-value link-header seed)
|
|
|
|
;; This is not adequate with respect to the RFC 5988 grammar, which
|
|
|
|
;; includes a wide variety of link-params other than "rel".
|
|
|
|
(define pieces
|
|
|
|
(append (regexp-match* #rx"(<([^>]+)>; +rel=([^\"][^,]*))" link-header #:match-select values)
|
|
|
|
(regexp-match* #rx"(<([^>]+)>; +rel=\"([^\"]+)\")" link-header #:match-select values)))
|
|
|
|
(for/fold [(rel-dict seed)] [(piece pieces)]
|
|
|
|
(match-define (list _ _ url rels-str) piece)
|
|
|
|
(for/fold [(rel-dict rel-dict)] [(rel-str (string-split rels-str))]
|
|
|
|
(define rel (string->symbol rel-str))
|
|
|
|
(hash-set rel-dict rel (cons url (hash-ref rel-dict rel '()))))))
|
|
|
|
|
2016-10-29 11:16:29 +00:00
|
|
|
(module+ test
|
2016-11-01 02:29:06 +00:00
|
|
|
(define combined "<http://phubb.cweiske.de/hub.php>; rel=\"hub\", <http://push-tester.cweiske.de/feed.php>; rel=\"self\"")
|
|
|
|
(define separate "<http://localhost:7827/hub>; rel=hub")
|
|
|
|
(check-equal? (parse-link-header-value combined (hasheq))
|
|
|
|
(hasheq 'hub (list "http://phubb.cweiske.de/hub.php")
|
|
|
|
'self (list "http://push-tester.cweiske.de/feed.php")))
|
|
|
|
(check-equal? (parse-link-header-value separate (hasheq))
|
|
|
|
(hasheq 'hub (list "http://localhost:7827/hub")))
|
|
|
|
(check-equal? (parse-link-headers (list (cons 'link combined)
|
|
|
|
(cons 'content-type "text/plain")
|
|
|
|
(cons 'link separate)))
|
|
|
|
(hasheq 'hub (list "http://phubb.cweiske.de/hub.php"
|
|
|
|
"http://localhost:7827/hub")
|
|
|
|
'self (list "http://push-tester.cweiske.de/feed.php"))))
|
2016-11-20 21:13:40 +00:00
|
|
|
|
|
|
|
(define (maybe-link-header urlstr rel)
|
|
|
|
(if urlstr
|
|
|
|
(list (cons 'link (format "<~a>; rel=~a" urlstr rel)))
|
|
|
|
'()))
|
|
|
|
|
|
|
|
(define (vh host-name port) (web-virtual-host "http" host-name port))
|
|
|
|
|
|
|
|
(define (canonical-url canonical-host-name cport path)
|
|
|
|
(url->string (resource->url (web-resource (vh canonical-host-name cport) path))))
|