1 ! Copyright (C) 2003, 2008 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 tools.vocabs tools.time math math.parser present
19 fry logging logging.insomniac calendar urls urls.encoding
31 : check-absolute ( url -- url )
32 dup path>> "/" head? [ "Bad request: URL" throw ] unless ; inline
34 : read-request-line ( request -- request )
35 read-crlf parse-request-line first3
36 [ >>method ] [ >url check-absolute >>url ] [ >>version ] tri* ;
38 : read-request-header ( request -- request )
39 read-header >>header ;
43 : parse-multipart-form-data ( string -- separator )
45 "=" split1 nip [ no-boundary ] unless* ;
47 : read-multipart-data ( request -- mime-parts )
48 [ "content-type" header ]
49 [ "content-length" header string>number ] bi
51 stream-eofs limit-input
53 parse-multipart-form-data parse-multipart ;
55 : read-content ( request -- bytes )
56 "content-length" header string>number read ;
58 : parse-content ( request content-type -- post-data )
59 [ <post-data> swap ] keep {
60 { "multipart/form-data" [ read-multipart-data >>params ] }
61 { "application/x-www-form-urlencoded" [ read-content query>assoc >>params ] }
62 [ drop read-content >>data ]
65 : read-post-data ( request -- request )
66 dup method>> "POST" = [
67 dup dup "content-type" header
68 ";" split1 drop parse-content >>post-data
71 : extract-host ( request -- request )
72 [ ] [ url>> ] [ "host" header parse-host ] tri
73 [ >>host ] [ >>port ] bi*
76 : extract-cookies ( request -- request )
77 dup "cookie" header [ parse-cookie >>cookies ] when* ;
79 : read-request ( -- request )
87 GENERIC: write-response ( response -- )
89 GENERIC: write-full-response ( request response -- )
91 : write-response-line ( response -- response )
93 [ "HTTP/" write version>> write bl ]
94 [ code>> present write bl ]
95 [ message>> write crlf ]
98 : unparse-content-type ( request -- content-type )
99 [ content-type>> "application/octet-stream" or ]
100 [ content-charset>> encoding>name ]
102 [ "; charset=" glue ] when* ;
104 : ensure-domain ( cookie -- cookie )
106 url get host>> dup "localhost" =
110 : write-response-header ( response -- response )
111 #! We send one set-cookie header per cookie, because that's
112 #! what Firefox expects.
113 dup header>> >alist >vector
114 over unparse-content-type "content-type" pick set-at
116 ensure-domain unparse-set-cookie
117 "set-cookie" swap 2array over push
121 : write-response-body ( response -- response )
122 dup body>> call-template ;
124 M: response write-response ( respose -- )
126 write-response-header
130 M: response write-full-response ( request response -- )
132 swap method>> "HEAD" = [
133 [ content-charset>> encode-output ]
134 [ write-response-body ]
138 M: raw-response write-response ( respose -- )
143 M: raw-response write-full-response ( response -- )
146 : post-request? ( -- ? ) request get method>> "POST" = ;
148 SYMBOL: responder-nesting
150 SYMBOL: main-responder
156 ! path is a sequence of path component strings
157 GENERIC: call-responder* ( path responder -- response )
159 TUPLE: trivial-responder response ;
161 C: <trivial-responder> trivial-responder
163 M: trivial-responder call-responder* nip response>> clone ;
165 main-responder global [ <404> <trivial-responder> or ] change-at
167 : invert-slice ( slice -- slice' )
168 dup slice? [ [ seq>> ] [ from>> ] bi head-slice ] [ drop { } ] if ;
170 : add-responder-nesting ( path responder -- )
171 [ invert-slice ] dip 2array responder-nesting get push ;
173 : call-responder ( path responder -- response )
174 [ add-responder-nesting ] [ call-responder* ] 2bi ;
176 : http-error. ( error -- )
177 "Internal server error" [ ] [
178 [ print-error nl :c ] with-html-writer
181 : <500> ( error -- response )
182 500 "Internal server error" <trivial-response>
183 swap development? get [ '[ _ http-error. ] >>body ] [ drop ] if ;
185 : do-response ( response -- )
186 [ request get swap write-full-response ]
188 [ \ do-response log-error ]
192 [ http-error. ] [ drop "Response error" write ] if
193 ] with-encoded-output
197 LOG: httpd-hit NOTICE
199 LOG: httpd-header NOTICE
201 : log-header ( request name -- )
202 [ nip ] [ header ] 2bi 2array httpd-header ;
204 : log-request ( request -- )
205 [ [ method>> ] [ url>> ] bi 2array httpd-hit ]
206 [ { "user-agent" "x-forwarded-for" } [ log-header ] with each ]
209 : split-path ( string -- path )
212 : init-request ( request -- )
213 [ request set ] [ url>> url set ] bi
214 V{ } clone responder-nesting set ;
216 : dispatch-request ( request -- response )
217 url>> path>> split-path main-responder get call-responder ;
219 : prepare-request ( request -- )
222 [ secure? "https" "http" ? >>protocol ]
223 [ port>> remap-port '[ _ or ] change-port ]
227 : valid-request? ( request -- ? )
228 url>> port>> remap-port
229 local-address get port>> remap-port = ;
231 : do-request ( request -- response )
238 [ dup valid-request? [ dispatch-request ] [ drop <400> ] if ]
240 ] [ [ \ do-request log-error ] [ <500> ] bi ] recover ;
242 : ?refresh-all ( -- )
243 development? get-global [ global [ refresh-all ] bind ] when ;
245 LOG: httpd-benchmark DEBUG
247 : ?benchmark ( quot -- )
249 [ benchmark ] [ first ] bi url get rot 3array
251 ] [ call ] if ; inline
253 TUPLE: http-server < threaded-server ;
255 M: http-server handle-client*
258 64 1024 * stream-throws limit-input
260 [ read-request ] ?benchmark
261 [ do-request ] ?benchmark
262 [ do-response ] ?benchmark
265 : <http-server> ( -- server )
266 http-server new-threaded-server
268 "http" protocol-port >>insecure
269 "https" protocol-port >>secure ;
277 : http-insomniac ( -- )
278 "http.server" { "httpd-hit" } schedule-insomniac ;
282 "http.server.filters" require
283 "http.server.dispatchers" require
284 "http.server.redirection" require
285 "http.server.static" require
286 "http.server.cgi" require