1 ! Copyright (C) 2005, 2010 Slava Pestov.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays ascii assocs calendar combinators
4 combinators.short-circuit destructors environment hashtables
5 http http.client.post-data http.parsers http.websockets io
6 io.crlf io.encodings io.encodings.ascii io.encodings.binary
7 io.encodings.iana io.encodings.string io.files io.pathnames
8 io.sockets io.sockets.secure io.timeouts kernel math math.order
9 math.parser mime.types namespaces present sequences splitting
13 ERROR: too-many-redirects ;
14 ERROR: invalid-proxy proxy ;
16 : success? ( code -- ? ) 200 299 between? ;
18 ERROR: download-failed response ;
20 : check-response ( response -- response )
21 dup code>> success? [ download-failed ] unless ;
25 : authority-uri ( url -- str )
26 [ host>> ] [ port>> number>string ] bi ":" glue ;
28 : absolute-uri ( url -- str )
29 clone f >>username f >>password f >>anchor present ;
31 : abs-path-uri ( url -- str )
32 relative-url f >>anchor present ;
34 : request-uri ( request -- str )
36 { [ dup proxy-url>> ] [ url>> absolute-uri ] }
37 { [ dup method>> "CONNECT" = ] [ url>> authority-uri ] }
38 [ url>> abs-path-uri ]
41 : write-request-line ( request -- request )
44 [ request-uri write bl ]
45 [ "HTTP/" write version>> write crlf ]
48 : default-port? ( url -- ? )
51 [ [ port>> ] [ protocol>> protocol-port ] bi = ]
54 : unparse-host ( url -- string )
55 dup default-port? [ host>> ] [
56 [ host>> ] [ port>> number>string ] bi ":" glue
59 : set-host-header ( request header -- request header )
60 over url>> unparse-host "Host" pick set-at ;
62 : set-cookie-header ( header cookies -- header )
63 unparse-cookie "Cookie" pick set-at ;
65 : ?set-basic-auth ( header url name -- header )
67 [ username>> ] [ password>> ] bi 2dup and
68 [ basic-auth swap pick set-at ] [ 3drop ] if
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
80 : write-request ( request -- )
89 : read-response-line ( response -- response )
90 read-?crlf parse-response-line first3
91 [ >>version ] [ >>code ] [ >>message ] tri* ;
93 : detect-encoding ( response -- encoding )
94 [ content-charset>> name>encoding ]
95 [ content-type>> mime-type-encoding ] bi
98 : read-response-header ( response -- response )
100 dup "set-cookie" header parse-set-cookie >>cookies
101 dup "content-type" header [
103 [ >>content-type ] [ >>content-charset ] bi*
104 dup detect-encoding >>content-encoding
107 : read-response ( -- response )
110 read-response-header ;
112 DEFER: (with-http-request)
116 : redirect-url ( request url -- request )
117 '[ _ >url derive-url ensure-port ] change-url ;
119 : redirect? ( response -- ? )
120 code>> 300 399 between? ;
122 :: do-redirect ( quot: ( chunk -- ) response -- response )
124 redirects get request get redirects>> < [
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
131 : read-chunk-size ( -- n )
132 read-crlf ";" split1 drop [ blank? ] trim-tail
133 hex> [ "Bad chunk size" throw ] unless* ;
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
141 : read-response-body ( quot: ( chunk -- ) response -- )
143 "transfer-encoding" header "chunked" =
144 [ read-chunked ] [ each-block ] if ; inline
146 : request-socket-endpoints ( request -- physical logical )
147 [ proxy-url>> ] [ url>> ] bi [ or ] keep ;
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 ;
154 : https-tunnel? ( request -- ? )
155 [ proxy-url>> ] [ url>> protocol>> "https" = ] bi and ;
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 ;
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
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 ;
173 : get-no-proxy-list ( -- list )
175 [ "no_proxy" os-env ] unless*
176 [ "NO_PROXY" os-env ] unless* ;
178 : no-proxy? ( request -- ? )
180 [ url>> host>> "." split ] dip "," split
181 [ "." split no-proxy-match? ] with any?
184 : (check-proxy) ( proxy -- ? )
186 { [ dup URL" " = ] [ drop f ] }
187 { [ dup host>> ] [ drop t ] }
191 : check-proxy ( request proxy -- request' )
192 dup [ (check-proxy) ] [ f ] if*
193 [ drop f ] unless [ clone ] dip >>proxy-url ;
195 : get-default-proxy ( request -- default-proxy )
196 url>> protocol>> "https" = [
198 [ "https_proxy" os-env ] unless*
199 [ "HTTPS_PROXY" os-env ] unless*
202 [ "http_proxy" os-env ] unless*
203 [ "HTTP_PROXY" os-env ] unless*
206 : misparsed-url? ( url -- url' )
207 [ protocol>> not ] [ host>> not ] [ path>> ] tri and and ;
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 ;
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 ;
221 SYMBOL: request-socket
222 : (with-http-request) ( request quot: ( chunk -- ) -- response/websocket )
226 <request-socket> |dispose
229 [ in>> ] [ out>> ] bi
230 [ ?https-tunnel ] with-streams*
234 [ request get write-request ]
239 { [ dup redirect? request get redirects>> 0 > and ] [ request-socket get &dispose drop t ] }
240 { [ dup check-websocket-upgraded? ] [ request-socket get 2array f ] }
242 request-socket get &dispose drop
244 [ read-response-body ]
252 [ do-redirect ] [ nip ] if
254 ] with-destructors ; inline recursive
256 : <client-request> ( url method -- request )
259 swap request-url >>url ; inline
263 : with-http-request* ( request quot: ( chunk -- ) -- response )
264 [ (with-http-request) ] with-destructors ; inline
266 : with-http-request ( request quot: ( chunk -- ) -- response )
267 with-http-request* check-response ; inline
269 : http-request* ( request -- response data/websocket-stream )
270 BV{ } clone [ '[ _ push-all ] with-http-request* ] keep
275 over content-encoding>> decode [ >>body ] keep
278 : http-request ( request -- response data )
279 http-request* [ check-response ] dip ;
281 : <get-request> ( url -- request )
282 "GET" <client-request> ;
284 : http-get ( url -- response data )
285 <get-request> http-request ;
287 : http-get* ( url -- response data )
288 <get-request> http-request* ;
290 : download-name ( url -- name )
291 present file-name "?" split1 drop "/" ?tail drop ;
293 : download-to ( url file -- )
295 <get-request> [ write ] with-http-request drop
298 : ?download-to ( url file -- )
299 dup file-exists? [ 2drop ] [ download-to ] if ;
301 : download ( url -- )
302 dup download-name download-to ;
304 : <post-request> ( post-data url -- request )
305 "POST" <client-request>
308 : http-post ( post-data url -- response data )
309 <post-request> http-request ;
311 : http-post* ( post-data url -- response data )
312 <post-request> http-request* ;
314 : <put-request> ( post-data url -- request )
315 "PUT" <client-request>
318 : http-put ( post-data url -- response data )
319 <put-request> http-request ;
321 : http-put* ( post-data url -- response data )
322 <put-request> http-request* ;
324 : <delete-request> ( url -- request )
325 "DELETE" <client-request> ;
327 : http-delete ( url -- response data )
328 <delete-request> http-request ;
330 : http-delete* ( url -- response data )
331 <delete-request> http-request* ;
333 : <head-request> ( url -- request )
334 "HEAD" <client-request> ;
336 : http-head ( url -- response data )
337 <head-request> http-request ;
339 : http-head* ( url -- response data )
340 <head-request> http-request* ;
342 : <options-request> ( url -- request )
343 "OPTIONS" <client-request> ;
345 : http-options ( url -- response data )
346 <options-request> http-request ;
348 : http-options* ( url -- response data )
349 <options-request> http-request* ;
351 : <trace-request> ( url -- request )
352 "TRACE" <client-request> ;
354 : http-trace ( url -- response data )
355 <trace-request> http-request ;
357 : http-trace* ( url -- response data )
358 <trace-request> http-request* ;
360 { "http.client" "debugger" } "http.client.debugger" require-when