1 ! Copyright (C) 2005, 2009 Slava Pestov.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors assocs debugger kernel math math.parser namespaces make
4 sequences strings splitting calendar continuations accessors vectors
5 math.order hashtables byte-arrays destructors
6 io io.sockets io.streams.string io.files io.timeouts
7 io.pathnames io.encodings io.encodings.string io.encodings.ascii
8 io.encodings.utf8 io.encodings.8-bit io.encodings.binary io.crlf
9 io.streams.duplex fry ascii urls urls.encoding present locals
10 http http.parsers http.client.post-data ;
13 ERROR: too-many-redirects ;
15 CONSTANT: max-redirects 10
19 : write-request-line ( request -- request )
22 [ url>> relative-url present write bl ]
23 [ "HTTP/" write version>> write crlf ]
26 : url-host ( url -- string )
27 [ host>> ] [ port>> ] bi dup "http" protocol-port =
28 [ drop ] [ ":" swap number>string 3append ] if ;
30 : set-host-header ( request header -- request header )
31 over url>> url-host "host" pick set-at ;
33 : set-cookie-header ( header cookies -- header )
34 unparse-cookie "cookie" pick set-at ;
36 : write-request-header ( request -- request )
37 dup header>> >hashtable
38 over url>> host>> [ set-host-header ] when
39 over post-data>> [ set-post-data-headers ] when*
40 over cookies>> [ set-cookie-header ] unless-empty
43 : write-request ( request -- )
52 : read-response-line ( response -- response )
53 read-crlf parse-response-line first3
54 [ >>version ] [ >>code ] [ >>message ] tri* ;
56 : read-response-header ( response -- response )
58 dup "set-cookie" header parse-set-cookie >>cookies
59 dup "content-type" header [
62 [ >>content-charset ] bi*
65 : read-response ( -- response )
68 read-response-header ;
70 DEFER: (with-http-request)
74 : redirect-url ( request url -- request )
75 '[ _ >url derive-url ensure-port ] change-url ;
77 : redirect? ( response -- ? )
78 code>> 300 399 between? ;
80 :: do-redirect ( quot: ( chunk -- ) response -- response )
82 redirects get max-redirects < [
84 response "location" header redirect-url
85 response code>> 307 = [ "GET" >>method ] unless
86 quot (with-http-request)
87 ] [ too-many-redirects ] if ; inline recursive
89 : read-chunk-size ( -- n )
90 read-crlf ";" split1 drop [ blank? ] trim-tail
91 hex> [ "Bad chunk size" throw ] unless* ;
93 : read-chunked ( quot: ( chunk -- ) -- )
94 read-chunk-size dup zero?
96 read [ swap call ] [ drop ] 2bi
97 read-crlf B{ } assert= read-chunked
98 ] if ; inline recursive
100 : read-response-body ( quot response -- )
102 "transfer-encoding" header "chunked" =
103 [ read-chunked ] [ each-block ] if ; inline
105 : <request-socket> ( -- stream )
106 request get url>> url-addr ascii <client> drop
107 1 minutes over set-timeout ;
109 : (with-http-request) ( request quot: ( chunk -- ) -- response )
115 [ request get write-request ]
119 read-response dup redirect? [ t ] [
121 [ read-response-body ]
128 [ do-redirect ] [ nip ] if
129 ] with-variable ; inline recursive
133 : <client-request> ( url method -- request )
136 swap >url ensure-port >>url ; inline
138 : success? ( code -- ? ) 200 299 between? ;
140 ! ERROR: download-failed response data ;
142 ! M: download-failed error.
143 ! "HTTP request failed:" print nl
144 ! [ response>> . ] [ data>> . ] bi ;
145 ERROR: download-failed response ;
147 : check-response ( response -- response )
148 dup code>> success? [ download-failed ] unless ;
149 ! : check-response ( response data -- response data )
150 ! over code>> success? [ download-failed ] unless ;
152 : check-response-with-body ( response body -- response body )
153 [ >>body check-response ] keep ;
155 : with-http-request ( request quot -- response )
156 [ (with-http-request) ] with-destructors ; inline
158 : http-request ( request -- response data )
159 [ [ % ] with-http-request ] B{ } make
160 over content-charset>> decode check-response-with-body ;
162 : <get-request> ( url -- request )
163 "GET" <client-request> ;
165 : http-get ( url -- response data )
166 <get-request> http-request ;
168 : with-http-get ( url quot -- response )
169 [ <get-request> ] dip with-http-request check-response ; inline
171 ! : <delete-request> ( url -- request )
172 ! "DELETE" <client-request> ;
174 ! : http-delete ( url -- response )
175 ! <delete-request> http-request ;
177 ! : <trace-request> ( url -- request )
178 ! <client-request> "TRACE" >>method ;
180 ! : http-trace ( url -- response )
181 ! <trace-request> http-request ;
183 : download-name ( url -- name )
184 present file-name "?" split1 drop "/" ?tail drop ;
186 : download-to ( url file -- )
187 binary [ [ write ] with-http-get check-response drop ] with-file-writer ;
189 : download ( url -- )
190 dup download-name download-to ;
192 : <post-request> ( post-data url -- request )
193 "POST" <client-request>
196 : http-post ( post-data url -- response data )
197 <post-request> http-request ;
199 : <put-request> ( post-data url -- request )
200 "PUT" <client-request>
203 : http-put ( post-data url -- response data )
204 <put-request> http-request ;
206 USING: vocabs vocabs.loader ;
208 "debugger" vocab [ "http.client.debugger" require ] when