]> gitweb.factorcode.org Git - factor.git/blob - basis/http/client/client.factor
io.files: exists? -> file-exists? and rename primitive.
[factor.git] / basis / http / client / client.factor
1 ! Copyright (C) 2005, 2010 Slava Pestov.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors ascii assocs calendar combinators.short-circuit
4 destructors fry hashtables http http.client.post-data
5 http.parsers io io.crlf io.encodings io.encodings.ascii
6 io.encodings.binary io.encodings.iana io.encodings.string
7 io.files io.pathnames io.sockets io.sockets.secure io.timeouts
8 kernel locals math math.order math.parser mime.types namespaces
9 present sequences splitting urls vocabs.loader combinators
10 environment ;
11 IN: http.client
12
13 ERROR: too-many-redirects ;
14 ERROR: invalid-proxy proxy ;
15
16 : success? ( code -- ? ) 200 299 between? ;
17
18 ERROR: download-failed response ;
19
20 : check-response ( response -- response )
21     dup code>> success? [ download-failed ] unless ;
22
23 <PRIVATE
24
25 : authority-uri ( url -- str )
26     [ host>> ] [ port>> number>string ] bi ":" glue ;
27
28 : absolute-uri ( url -- str )
29     clone f >>username f >>password f >>anchor present ;
30
31 : abs-path-uri ( url -- str )
32     relative-url f >>anchor present ;
33
34 : request-uri ( request -- str )
35     {
36         { [ dup proxy-url>> ] [ url>> absolute-uri ] }
37         { [ dup method>> "CONNECT" = ] [ url>> authority-uri ] }
38         [ url>> abs-path-uri ]
39     } cond ;
40
41 : write-request-line ( request -- request )
42     dup
43     [ method>> write bl ]
44     [ request-uri write bl ]
45     [ "HTTP/" write version>> write crlf ]
46     tri ;
47
48 : default-port? ( url -- ? )
49     {
50         [ port>> not ]
51         [ [ port>> ] [ protocol>> protocol-port ] bi = ]
52     } 1|| ;
53
54 : unparse-host ( url -- string )
55     dup default-port? [ host>> ] [
56         [ host>> ] [ port>> number>string ] bi ":" glue
57     ] if ;
58
59 : set-host-header ( request header -- request header )
60     over url>> unparse-host "Host" pick set-at ;
61
62 : set-cookie-header ( header cookies -- header )
63     unparse-cookie "Cookie" pick set-at ;
64
65 : ?set-basic-auth ( header url name -- header )
66     swap [
67         [ username>> ] [ password>> ] bi 2dup and
68         [ basic-auth swap pick set-at ] [ 3drop ] if
69     ] [ drop ] if* ;
70
71 : write-request-header ( request -- request )
72     dup header>> >hashtable
73     over url>> host>> [ set-host-header ] when
74     over url>> "Authorization" ?set-basic-auth
75     over proxy-url>> "Proxy-Authorization" ?set-basic-auth
76     over post-data>> [ set-post-data-headers ] when*
77     over cookies>> [ set-cookie-header ] unless-empty
78     write-header ;
79
80 : write-request ( request -- )
81     unparse-post-data
82     write-request-line
83     write-request-header
84     binary encode-output
85     write-post-data
86     flush
87     drop ;
88
89 : read-response-line ( response -- response )
90     read-?crlf parse-response-line first3
91     [ >>version ] [ >>code ] [ >>message ] tri* ;
92
93 : detect-encoding ( response -- encoding )
94     [ content-charset>> name>encoding ]
95     [ content-type>> mime-type-encoding ] bi
96     or ;
97
98 : read-response-header ( response -- response )
99     read-header >>header
100     dup "set-cookie" header parse-set-cookie >>cookies
101     dup "content-type" header [
102         parse-content-type
103         [ >>content-type ] [ >>content-charset ] bi*
104         dup detect-encoding >>content-encoding
105     ] when* ;
106
107 : read-response ( -- response )
108     <response>
109     read-response-line
110     read-response-header ;
111
112 DEFER: (with-http-request)
113
114 SYMBOL: redirects
115
116 : redirect-url ( request url -- request )
117     '[ _ >url derive-url ensure-port ] change-url ;
118
119 : redirect? ( response -- ? )
120     code>> 300 399 between? ;
121
122 :: do-redirect ( quot: ( chunk -- ) response -- response )
123     redirects inc
124     redirects get request get redirects>> < [
125         request get clone
126         response "location" header redirect-url
127         response code>> 307 = [ "GET" >>method f >>post-data ] unless
128         quot (with-http-request)
129     ] [ too-many-redirects ] if ; inline recursive
130
131 : read-chunk-size ( -- n )
132     read-crlf ";" split1 drop [ blank? ] trim-tail
133     hex> [ "Bad chunk size" throw ] unless* ;
134
135 : read-chunked ( quot: ( chunk -- ) -- )
136     read-chunk-size [ drop ] [
137         read [ swap call ] [ drop ] 2bi
138         read-crlf B{ } assert= read-chunked
139     ] if-zero ; inline recursive
140
141 : read-response-body ( quot: ( chunk -- ) response -- )
142     binary decode-input
143     "transfer-encoding" header "chunked" =
144     [ read-chunked ] [ each-block ] if ; inline
145
146 : request-socket-endpoints ( request -- physical logical )
147     [ proxy-url>> ] [ url>> ] bi [ or ] keep ;
148
149 : <request-socket> ( -- stream )
150     request get request-socket-endpoints [ url-addr ] bi@
151     remote-address set ascii <client> local-address set
152     1 minutes over set-timeout ;
153
154 : https-tunnel? ( request -- ? )
155     [ proxy-url>> ] [ url>> protocol>> "https" = ] bi and ;
156
157 : ?copy-proxy-basic-auth ( dst-request src-request -- dst-request )
158     proxy-url>> [ username>> ] [ password>> ] bi 2dup and
159     [ set-proxy-basic-auth ] [ 2drop ] if ;
160
161 : ?https-tunnel ( -- )
162     request get dup https-tunnel? [
163         <request> swap [ url>> >>url ] [ ?copy-proxy-basic-auth ] bi
164         f >>proxy-url "CONNECT" >>method write-request
165         read-response check-response drop send-secure-handshake
166     ] [ drop ] if ;
167
168 ! Note: ipv4 addresses are interpreted as subdomains but "work"
169 : no-proxy-match? ( host-path no-proxy-path -- ? )
170     dup first empty? [ [ rest ] bi@ ] when
171     [ drop f ] [ tail? ] if-empty ;
172
173 : get-no-proxy-list ( -- list )
174     "no_proxy" get
175     [ "no_proxy" os-env ] unless*
176     [ "NO_PROXY" os-env ] unless* ;
177
178 : no-proxy? ( request -- ? )
179     get-no-proxy-list [
180        [ url>> host>> "." split ] dip "," split
181        [ "." split no-proxy-match? ] with any?
182     ] [ drop f ] if* ;
183
184 : (check-proxy) ( proxy -- ? )
185     {
186         { [ dup URL" " = ] [ drop f ] }
187         { [ dup host>> ] [ drop t ] }
188         [ invalid-proxy ]
189     } cond ;
190
191 : check-proxy ( request proxy -- request' )
192     dup [ (check-proxy) ] [ f ] if*
193     [ drop f ] unless [ clone ] dip >>proxy-url ;
194
195 : get-default-proxy ( request -- default-proxy )
196     url>> protocol>> "https" = [
197         "https.proxy" get
198         [ "https_proxy" os-env ] unless*
199         [ "HTTPS_PROXY" os-env ] unless*
200     ] [
201         "http.proxy" get
202         [ "http_proxy" os-env ] unless*
203         [ "HTTP_PROXY" os-env ] unless*
204     ] if ;
205
206 : misparsed-url? ( url -- url' )
207     [ protocol>> not ] [ host>> not ] [ path>> ] tri and and ;
208
209 : request-url ( url -- url' )
210     dup >url dup misparsed-url? [
211         drop dup url? [ present ] when
212         "http://" prepend >url
213     ] [ nip ] if ensure-port ;
214
215 : ?default-proxy ( request -- request' )
216     dup get-default-proxy
217     over proxy-url>> dup [ request-url ] when 2dup and [
218         pick no-proxy? [ nip ] [ [ request-url ] dip derive-url ] if
219     ] [ nip ] if check-proxy ;
220
221 : (with-http-request) ( request quot: ( chunk -- ) -- response )
222     swap ?default-proxy
223     request [
224         <request-socket> [
225             [
226                 [ in>> ] [ out>> ] bi
227                 [ ?https-tunnel ] with-streams*
228             ]
229             [
230                 out>>
231                 [ request get write-request ]
232                 with-output-stream*
233             ] [
234                 in>> [
235                     read-response dup redirect?
236                     request get redirects>> 0 > and [ t ] [
237                         [ nip response set ]
238                         [ read-response-body ]
239                         [ ]
240                         2tri f
241                     ] if
242                 ] with-input-stream*
243             ] tri
244         ] with-disposal
245         [ do-redirect ] [ nip ] if
246     ] with-variable ; inline recursive
247
248 : <client-request> ( url method -- request )
249     <request>
250         swap >>method
251         swap request-url >>url ; inline
252
253 PRIVATE>
254
255 : with-http-request* ( request quot: ( chunk -- ) -- response )
256     [ (with-http-request) ] with-destructors ; inline
257
258 : with-http-request ( request quot: ( chunk -- ) -- response )
259     with-http-request* check-response ; inline
260
261 : http-request* ( request -- response data )
262     BV{ } clone [ '[ _ push-all ] with-http-request* ] keep
263     B{ } like over content-encoding>> decode [ >>body ] keep ;
264
265 : http-request ( request -- response data )
266     http-request* [ check-response ] dip ;
267
268 : <get-request> ( url -- request )
269     "GET" <client-request> ;
270
271 : http-get ( url -- response data )
272     <get-request> http-request ;
273
274 : http-get* ( url -- response data )
275     <get-request> http-request* ;
276
277 : download-name ( url -- name )
278     present file-name "?" split1 drop "/" ?tail drop ;
279
280 : download-to ( url file -- )
281     binary [
282         <get-request> [ write ] with-http-request drop
283     ] with-file-writer ;
284
285 : ?download-to ( url file -- )
286     dup file-exists? [ 2drop ] [ download-to ] if ;
287
288 : download ( url -- )
289     dup download-name download-to ;
290
291 : <post-request> ( post-data url -- request )
292     "POST" <client-request>
293         swap >>post-data ;
294
295 : http-post ( post-data url -- response data )
296     <post-request> http-request ;
297
298 : http-post* ( post-data url -- response data )
299     <post-request> http-request* ;
300
301 : <put-request> ( post-data url -- request )
302     "PUT" <client-request>
303         swap >>post-data ;
304
305 : http-put ( post-data url -- response data )
306     <put-request> http-request ;
307
308 : http-put* ( post-data url -- response data )
309     <put-request> http-request* ;
310
311 : <delete-request> ( url -- request )
312     "DELETE" <client-request> ;
313
314 : http-delete ( url -- response data )
315     <delete-request> http-request ;
316
317 : http-delete* ( url -- response data )
318     <delete-request> http-request* ;
319
320 : <head-request> ( url -- request )
321     "HEAD" <client-request> ;
322
323 : http-head ( url -- response data )
324     <head-request> http-request ;
325
326 : http-head* ( url -- response data )
327     <head-request> http-request* ;
328
329 : <options-request> ( url -- request )
330     "OPTIONS" <client-request> ;
331
332 : http-options ( url -- response data )
333     <options-request> http-request ;
334
335 : http-options* ( url -- response data )
336     <options-request> http-request* ;
337
338 : <trace-request> ( url -- request )
339     "TRACE" <client-request> ;
340
341 : http-trace ( url -- response data )
342     <trace-request> http-request ;
343
344 : http-trace* ( url -- response data )
345     <trace-request> http-request* ;
346
347 { "http.client" "debugger" } "http.client.debugger" require-when