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.timeouts kernel locals math
8 math.order math.parser mime.types namespaces present sequences
9 splitting urls vocabs.loader ;
12 ERROR: too-many-redirects ;
16 : write-request-line ( request -- request )
19 [ url>> relative-url present write bl ]
20 [ "HTTP/" write version>> write crlf ]
23 : default-port? ( url -- ? )
26 [ [ port>> ] [ protocol>> protocol-port ] bi = ]
29 : unparse-host ( url -- string )
30 dup default-port? [ host>> ] [
31 [ host>> ] [ port>> number>string ] bi ":" glue
34 : set-host-header ( request header -- request header )
35 over url>> unparse-host "host" pick set-at ;
37 : set-cookie-header ( header cookies -- header )
38 unparse-cookie "cookie" pick set-at ;
40 : write-request-header ( request -- request )
41 dup header>> >hashtable
42 over url>> host>> [ set-host-header ] when
43 over post-data>> [ set-post-data-headers ] when*
44 over cookies>> [ set-cookie-header ] unless-empty
47 : write-request ( request -- )
56 : read-response-line ( response -- response )
57 read-?crlf parse-response-line first3
58 [ >>version ] [ >>code ] [ >>message ] tri* ;
60 : detect-encoding ( response -- encoding )
61 [ content-charset>> name>encoding ]
62 [ content-type>> mime-type-encoding ] bi
65 : read-response-header ( response -- response )
67 dup "set-cookie" header parse-set-cookie >>cookies
68 dup "content-type" header [
70 [ >>content-type ] [ >>content-charset ] bi*
71 dup detect-encoding >>content-encoding
74 : read-response ( -- response )
77 read-response-header ;
79 DEFER: (with-http-request)
83 : redirect-url ( request url -- request )
84 '[ _ >url derive-url ensure-port ] change-url ;
86 : redirect? ( response -- ? )
87 code>> 300 399 between? ;
89 :: do-redirect ( quot: ( chunk -- ) response -- response )
91 redirects get request get redirects>> < [
93 response "location" header redirect-url
94 response code>> 307 = [ "GET" >>method ] unless
95 quot (with-http-request)
96 ] [ too-many-redirects ] if ; inline recursive
98 : read-chunk-size ( -- n )
99 read-crlf ";" split1 drop [ blank? ] trim-tail
100 hex> [ "Bad chunk size" throw ] unless* ;
102 : read-chunked ( quot: ( chunk -- ) -- )
103 read-chunk-size dup zero?
105 read [ swap call ] [ drop ] 2bi
106 read-crlf B{ } assert= read-chunked
107 ] if ; inline recursive
109 : read-response-body ( quot: ( chunk -- ) response -- )
111 "transfer-encoding" header "chunked" =
112 [ read-chunked ] [ each-block ] if ; inline
114 : <request-socket> ( -- stream )
115 request get url>> url-addr ascii <client> drop
116 1 minutes over set-timeout ;
118 : (with-http-request) ( request quot: ( chunk -- ) -- response )
124 [ request get write-request ]
128 read-response dup redirect?
129 request get redirects>> 0 > and [ t ] [
131 [ read-response-body ]
138 [ do-redirect ] [ nip ] if
139 ] with-variable ; inline recursive
141 : request-url ( url -- url' )
142 dup >url dup protocol>> [ nip ] [
143 drop dup url? [ present ] when
144 "http://" prepend >url
147 : <client-request> ( url method -- request )
150 swap request-url >>url ; inline
154 : success? ( code -- ? ) 200 299 between? ;
156 ERROR: download-failed response ;
158 : check-response ( response -- response )
159 dup code>> success? [ download-failed ] unless ;
161 : with-http-request* ( request quot: ( chunk -- ) -- response )
162 [ (with-http-request) ] with-destructors ; inline
164 : with-http-request ( request quot: ( chunk -- ) -- response )
165 with-http-request* check-response ; inline
167 : http-request* ( request -- response data )
168 BV{ } clone [ '[ _ push-all ] with-http-request* ] keep
169 B{ } like over content-encoding>> decode [ >>body ] keep ;
171 : http-request ( request -- response data )
172 http-request* [ check-response ] dip ;
174 : <get-request> ( url -- request )
175 "GET" <client-request> ;
177 : http-get ( url -- response data )
178 <get-request> http-request ;
180 : http-get* ( url -- response data )
181 <get-request> http-request* ;
183 : download-name ( url -- name )
184 present file-name "?" split1 drop "/" ?tail drop ;
186 : download-to ( url file -- )
188 <get-request> [ write ] with-http-request drop
191 : ?download-to ( url file -- )
192 dup exists? [ 2drop ] [ download-to ] if ;
194 : download ( url -- )
195 dup download-name download-to ;
197 : <post-request> ( post-data url -- request )
198 "POST" <client-request>
201 : http-post ( post-data url -- response data )
202 <post-request> http-request ;
204 : http-post* ( post-data url -- response data )
205 <post-request> http-request* ;
207 : <put-request> ( post-data url -- request )
208 "PUT" <client-request>
211 : http-put ( post-data url -- response data )
212 <put-request> http-request ;
214 : http-put* ( post-data url -- response data )
215 <put-request> http-request* ;
217 : <delete-request> ( url -- request )
218 "DELETE" <client-request> ;
220 : http-delete ( url -- response data )
221 <delete-request> http-request ;
223 : http-delete* ( url -- response data )
224 <delete-request> http-request* ;
226 : <head-request> ( url -- request )
227 "HEAD" <client-request> ;
229 : http-head ( url -- response data )
230 <head-request> http-request ;
232 : http-head* ( url -- response data )
233 <head-request> http-request* ;
235 : <options-request> ( url -- request )
236 "OPTIONS" <client-request> ;
238 : http-options ( url -- response data )
239 <options-request> http-request ;
241 : http-options* ( url -- response data )
242 <options-request> http-request* ;
244 : <trace-request> ( url -- request )
245 "TRACE" <client-request> ;
247 : http-trace ( url -- response data )
248 <trace-request> http-request ;
250 : http-trace* ( url -- response data )
251 <trace-request> http-request* ;
253 { "http.client" "debugger" } "http.client.debugger" require-when