|
1 | 1 | #lang at-exp racket/base |
2 | 2 |
|
3 | | -(require racket/pretty |
4 | | - racket/string |
5 | | - racket/list |
6 | | - racket/hash |
7 | | - racket/port |
8 | | - racket/match |
9 | | - racket/format |
10 | | - racket/generic |
11 | | - net/http-client |
12 | | - net/uri-codec |
13 | | - net/url-string |
14 | | - json |
15 | | - xml |
16 | | - html-parsing |
17 | | - (for-syntax racket/base racket/list)) |
| 3 | +(require racket/generic |
| 4 | + (for-syntax racket/base racket/list) |
| 5 | + (file "./private/params.rkt") |
| 6 | + (file "./private/core.rkt")) |
| 7 | +(provide (except-out (all-defined-out) define-http-methods) ;; TODO: add contracts to http-get/post... |
| 8 | + (all-from-out (file "./private/params.rkt")) |
| 9 | + (all-from-out (file "./private/core.rkt"))) |
18 | 10 |
|
19 | | -(provide (except-out (all-defined-out) |
20 | | - define-http-methods |
21 | | - format-kv |
22 | | - pp-kv) |
23 | | - ;; TODO: add contracts to http-get/post... |
24 | | - ) |
25 | | - |
26 | | -(define current-http-user-agent |
27 | | - (make-parameter @~a{http-client[@(system-type)/@(system-type 'vm)-@(version)]})) |
28 | | -(define current-http-response-auto (make-parameter #t)) |
29 | 11 |
|
30 | 12 | ;; TODO: enhance below syntax defining code with syntax-case/parse.... |
31 | 13 | (define-syntax (define-http-methods stx) |
|
46 | 28 | (datum->syntax stx code)) |
47 | 29 | (define-http-methods get head post put delete options patch) |
48 | 30 |
|
49 | | -;;;; code Example of (define-http-methods get) |
| 31 | +;;;;;;; code Example of (define-http-methods get) |
50 | 32 | ;; (define (http-get url #:data [data (hasheq)] |
51 | 33 | ;; #:path [path ""] |
52 | 34 | ;; #:headers [headers (hasheq)]) |
|
56 | 38 | ;; url)) |
57 | 39 | ;; (http-do 'get conn #:data data #:path path #:headers headers)) |
58 | 40 |
|
59 | | -(struct http-connection (url headers data) |
60 | | - #:property prop:procedure |
61 | | - (lambda (self method |
62 | | - #:path [path ""] |
63 | | - #:data [data (hasheq)] |
64 | | - #:headers [headers (hasheq)]) |
65 | | - (http-do method self #:data data #:path path #:headers headers)) |
66 | | - #:methods gen:custom-write |
67 | | - [(define (write-proc self port mode) |
68 | | - (display @~a{#<http-connection @(~v (http-connection-url self)) @(pp-kv "headers" @(http-connection-headers self)) @(pp-kv "data" @(http-connection-data self))>} |
69 | | - port))]) |
70 | | - |
71 | | -;; TODO: http-request should be derived from http-connection |
72 | | -(struct http-request (url method headers data) |
73 | | - #:methods gen:custom-write |
74 | | - [(define (write-proc rqt port mode) |
75 | | - (display @~a{#<http-request @(string-upcase (~a (http-request-method rqt))) @(~v (http-request-url rqt)) @(pp-kv "headers" @(http-request-headers rqt)) @(pp-kv "data" @(http-request-data rqt))>} port))]) |
76 | | - |
77 | | -(struct http-response (request code headers body) |
78 | | - #:methods gen:custom-write |
79 | | - [(define (write-proc self port mode) |
80 | | - (define rqt (http-response-request self)) |
81 | | - (define rqt-txt @~a{@(string-upcase (~a (http-request-method rqt))) @(~v (http-request-url rqt))}) |
82 | | - (display @~a{#<http-response #<request @|rqt-txt|> @(pp-kv "code" @(http-response-code self)) @(pp-kv "headers" @(http-response-headers self)) @(pp-kv "body" @(http-response-body self))>} port))]) |
83 | | - |
84 | | -(define (format-kv k v) |
85 | | - (define length (string-length (~a v))) |
86 | | - (define marker @~a{......[@length]}) |
87 | | - @~a{@|k|: @(~v @v #:max-width 128 #:limit-marker @marker)}) |
88 | | - |
89 | | -(pretty-print-depth 1) |
90 | | -(define (pp-kv k v) |
91 | | - @~a{@|k|: @(pretty-format v 'infinity)}) |
92 | | -;; (pretty-print-size-hook (lambda (a b c) 1)) |
93 | | - |
94 | | -(define (http-do method conn |
95 | | - #:data [data1 (hasheq)] |
96 | | - #:path [path ""] |
97 | | - #:headers [headers1 (hasheq)]) |
98 | | - |
99 | | - (define url (string->url (http-connection-url conn))) |
100 | | - (define data2 (http-connection-data conn)) |
101 | | - (define data3 (make-hash (url-query url))) |
102 | | - (define headers2 (http-connection-headers conn)) |
103 | | - |
104 | | - (define req-path1 |
105 | | - (for/list ([e (url-path url)]) |
106 | | - (string-append "/" (path/param-path e)))) |
107 | | - (define req-path2 |
108 | | - (for/list ([e (string-split path "/")]) |
109 | | - (string-append "/" e))) |
110 | | - (define req-path1&2 (append req-path1 req-path2)) |
111 | | - |
112 | | - (define req-host (url-host url)) |
113 | | - (define req-path (if (empty? req-path1&2) |
114 | | - "/" |
115 | | - (string-join req-path1&2 ""))) |
116 | | - |
117 | | - (define req-headers (hash-union headers1 headers2 (hasheq 'User-Agent (current-http-user-agent)) |
118 | | - #:combine/key (lambda (k v1 v2) v1))) |
119 | | - (define req-data (hash-union data1 data2 data3 |
120 | | - #:combine/key (lambda (k v1 v2) v1))) |
121 | | - (define req (http-request (string-append (url-scheme url) |
122 | | - "://" |
123 | | - req-host |
124 | | - (if (url-port url) (number->string (url-port url)) "") |
125 | | - req-path) |
126 | | - method req-headers req-data)) |
127 | | - |
128 | | - (define req-headers-raw |
129 | | - (hash-map req-headers |
130 | | - (lambda (k v) (~a k ": " v)))) |
131 | | - (define req-data-raw |
132 | | - (match req-headers |
133 | | - ;; [(? hash-empty?) ""] |
134 | | - [(hash-table ('Accept "application/json")) (jsexpr->string req-data)] |
135 | | - ;; [(hash-table ('Accept "application/x-www-form-urlencoded")) (alist->form-urlencoded (hash->list req-data))] |
136 | | - [_ (alist->form-urlencoded (hash-map req-data |
137 | | - (lambda (k v) |
138 | | - (cons k (if (number? v) |
139 | | - (number->string v) |
140 | | - v)))))])) |
141 | | - |
142 | | - (define-values (res-status-raw res-headers-raw res-in) |
143 | | - (http-sendrecv req-host req-path |
144 | | - #:ssl? (match (url-scheme url) ["https" #t] [_ #f]) |
145 | | - #:method (string-upcase (symbol->string method)) |
146 | | - #:port (match (url-port url) |
147 | | - [(? integer? n) n] |
148 | | - [#f #:when (string=? (url-scheme url) "https") |
149 | | - 443] |
150 | | - [_ 80]) |
151 | | - #:headers req-headers-raw |
152 | | - #:data req-data-raw)) |
153 | | - (define res-body-raw (port->string res-in)) |
154 | | - |
155 | | - (define res-code |
156 | | - (string->number (second (string-split (bytes->string/utf-8 res-status-raw))))) |
157 | | - (define res-headers |
158 | | - (for/hasheq ([e res-headers-raw]) |
159 | | - (match (string-split (bytes->string/utf-8 e) ":") |
160 | | - [(list-rest a b) |
161 | | - (define k (string->symbol a)) |
162 | | - (define v (string-trim (string-join b))) |
163 | | - (values k v)]))) |
164 | | - |
165 | | - (define res-body |
166 | | - (match res-headers |
167 | | - [_ #:when (not (current-http-response-auto)) |
168 | | - res-body-raw] |
169 | | - [(hash-table ('Content-Type (regexp #rx"^application/json.*"))) |
170 | | - (string->jsexpr res-body-raw)] |
171 | | - [(hash-table ('Content-Type (regexp #rx"^text/html.*"))) |
172 | | - (html->xexp res-body-raw)] |
173 | | - [(hash-table ('Content-Type (regexp #rx"^(application/xml|text/xml|application/xhtml+xml).*"))) |
174 | | - (string->xexpr res-body-raw)] |
175 | | - [_ res-body-raw])) |
176 | | - |
177 | | - (http-response req res-code res-headers res-body)) |
178 | 41 |
|
179 | 42 |
|
| 43 | +;;;; =========> test ::: |
180 | 44 | (module+ test |
181 | 45 | (require rackunit) |
182 | 46 |
|
|
186 | 50 | (http-connection "https://httpbin.org/anything" (hasheq 'Content-Type "application/json" 'Accept "application/json") (hasheq 'made-in "China" 'price 10))) |
187 | 51 |
|
188 | 52 |
|
189 | | - (check-true (current-http-response-auto)) |
| 53 | + (check-true (current-http-client/response-auto)) |
190 | 54 |
|
191 | 55 | (let* ([res (http-get conn)] |
192 | 56 | [res-headers (http-response-headers res)] |
|
196 | 60 | "text/html; charset=utf-8") |
197 | 61 | (check-true (list? (http-response-body res)))) |
198 | 62 |
|
199 | | - (parameterize ([current-http-response-auto #f]) |
200 | | - (check-false (current-http-response-auto)) |
| 63 | + (parameterize ([current-http-client/response-auto #f]) |
| 64 | + (check-false (current-http-client/response-auto)) |
201 | 65 | (define res (http-get conn)) |
202 | 66 | (define res-headers (http-response-headers res)) |
203 | 67 | (define res-body (http-response-body res)) |
|
0 commit comments