1 ! Copyright (C) 2003, 2010 Slava Pestov.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: kernel accessors sequences arrays namespaces splitting
4 vocabs.loader destructors assocs debugger continuations
5 combinators vocabs.refresh tools.time math math.parser present
21 fry logging logging.insomniac calendar urls urls.encoding
33 FROM: mime.multipart => parse-multipart ;
36 : check-absolute ( url -- url )
37 dup path>> "/" head? [ "Bad request: URL" throw ] unless ; inline
39 : read-request-line ( request -- request )
40 read-?crlf [ dup empty? ] [ drop read-?crlf ] while
41 parse-request-line first3
42 [ >>method ] [ >url check-absolute >>url ] [ >>version ] tri* ;
44 : read-request-header ( request -- request )
45 read-header >>header ;
49 : parse-multipart-form-data ( string -- separator )
51 "=" split1 nip [ no-boundary ] unless* ;
55 request-limit [ 64 1024 * ] initialize
59 upload-limit [ 200,000,000 ] initialize
61 : read-multipart-data ( request -- mime-parts )
62 [ "content-type" header ]
63 [ "content-length" header string>number ] bi
65 upload-limit get [ min ] when* limited-input
67 parse-multipart-form-data parse-multipart ;
69 : read-content ( request -- bytes )
70 "content-length" header string>number read ;
72 : parse-content ( request content-type -- post-data )
73 [ <post-data> swap ] keep {
74 { "multipart/form-data" [ read-multipart-data >>params ] }
75 { "application/x-www-form-urlencoded" [ read-content query>assoc >>params ] }
76 [ drop read-content >>data ]
79 : read-post-data ( request -- request )
80 dup method>> "POST" = [
81 dup dup "content-type" header
82 ";" split1 drop parse-content >>post-data
85 : extract-host ( request -- request )
86 [ ] [ url>> ] [ "host" header parse-host ] tri
87 [ >>host ] [ >>port ] bi*
90 : extract-cookies ( request -- request )
91 dup "cookie" header [ parse-cookie >>cookies ] when* ;
93 : read-request ( -- request )
101 GENERIC: write-response ( response -- )
103 GENERIC: write-full-response ( request response -- )
105 : write-response-line ( response -- response )
107 [ "HTTP/" write version>> write bl ]
108 [ code>> present write bl ]
109 [ message>> write crlf ]
112 : unparse-content-type ( request -- content-type )
113 [ content-type>> ] [ content-charset>> ] bi
114 over mime-type-encoding encoding>name or
115 [ "application/octet-stream" or ] dip
116 [ "; charset=" glue ] when* ;
118 : ensure-domain ( cookie -- cookie )
120 url get host>> dup "localhost" =
124 : write-response-header ( response -- response )
125 #! We send one set-cookie header per cookie, because that's
126 #! what Firefox expects.
127 dup header>> >alist >vector
128 over unparse-content-type "content-type" pick set-at
130 ensure-domain unparse-set-cookie
131 "set-cookie" swap 2array over push
135 : write-response-body ( response -- response )
136 dup body>> call-template ;
138 M: response write-response ( respose -- )
140 write-response-header
144 M: response write-full-response ( request response -- )
146 swap method>> "HEAD" = [
147 [ content-encoding>> encode-output ]
148 [ write-response-body ]
152 M: raw-response write-response ( respose -- )
157 M: raw-response write-full-response ( request response -- )
160 : post-request? ( -- ? ) request get method>> "POST" = ;
162 SYMBOL: responder-nesting
164 SYMBOL: main-responder
170 ! path is a sequence of path component strings
171 GENERIC: call-responder* ( path responder -- response )
173 TUPLE: trivial-responder response ;
175 C: <trivial-responder> trivial-responder
177 M: trivial-responder call-responder* nip response>> clone ;
179 main-responder [ <404> <trivial-responder> ] initialize
181 : invert-slice ( slice -- slice' )
182 dup slice? [ [ seq>> ] [ from>> ] bi head-slice ] [ drop { } ] if ;
184 : add-responder-nesting ( path responder -- )
185 [ invert-slice ] dip 2array responder-nesting get push ;
187 : call-responder ( path responder -- response )
188 [ add-responder-nesting ] [ call-responder* ] 2bi ;
190 : make-http-error ( error -- xml )
191 [ "Internal server error" f ] dip
192 [ print-error nl :c ] with-html-writer
195 : <500> ( error -- response )
196 500 "Internal server error" <trivial-response>
197 swap development? get [ make-http-error >>body ] [ drop ] if ;
199 : do-response ( response -- )
200 '[ request get _ write-full-response ]
202 [ \ do-response log-error ]
206 [ make-http-error ] [ drop "Response error" ] if
208 ] with-encoded-output
212 LOG: httpd-hit NOTICE
214 LOG: httpd-header NOTICE
216 : log-header ( request name -- )
217 [ nip ] [ header ] 2bi 2array httpd-header ;
219 : log-request ( request -- )
220 [ [ method>> ] [ url>> ] bi 2array httpd-hit ]
221 [ { "user-agent" "x-forwarded-for" } [ log-header ] with each ]
224 : split-path ( string -- path )
227 : request-params ( request -- assoc )
229 { "GET" [ url>> query>> ] }
230 { "HEAD" [ url>> query>> ] }
231 { "POST" [ post-data>> params>> ] }
236 : param ( name -- value )
239 : set-param ( value name -- )
242 : init-request ( request -- )
245 [ request-params >hashtable params set ] tri
246 V{ } clone responder-nesting set ;
248 : dispatch-request ( request -- response )
249 url>> path>> split-path main-responder get call-responder ;
251 : prepare-request ( request -- )
254 [ secure? "https" "http" ? >>protocol ]
255 [ port>> remap-port '[ _ or ] change-port ]
259 : valid-request? ( request -- ? )
260 url>> port>> remap-port
261 local-address get port>> remap-port = ;
263 : do-request ( request -- response )
270 [ dup valid-request? [ dispatch-request ] [ drop <400> ] if ]
272 ] [ [ \ do-request log-error ] [ <500> ] bi ] recover ;
274 : ?refresh-all ( -- )
275 development? get-global [ [ refresh-all ] with-global ] when ;
277 LOG: httpd-benchmark DEBUG
279 : ?benchmark ( quot -- )
281 [ benchmark ] [ first ] bi url get rot 3array
283 ] [ call ] if ; inline
285 TUPLE: http-server < threaded-server ;
287 M: http-server handle-client*
290 request-limit get limited-input
291 [ read-request ] ?benchmark
292 [ do-request ] ?benchmark
293 [ do-response ] ?benchmark
296 : <http-server> ( -- server )
297 ascii http-server new-threaded-server
299 "http" protocol-port >>insecure
300 "https" protocol-port >>secure ;
302 : httpd ( port -- http-server )
308 : http-insomniac ( -- )
309 "http.server" { "httpd-hit" } schedule-insomniac ;
311 "http.server.filters" require
312 "http.server.dispatchers" require
313 "http.server.redirection" require
314 "http.server.static" require
315 "http.server.cgi" require