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
17 fry logging logging.insomniac calendar urls
26 : check-absolute ( url -- url )
27 dup path>> "/" head? [ "Bad request: URL" throw ] unless ; inline
29 : read-request-line ( request -- request )
30 read-crlf parse-request-line first3
31 [ >>method ] [ >url check-absolute >>url ] [ >>version ] tri* ;
33 : read-request-header ( request -- request )
34 read-header >>header ;
36 : parse-post-data ( post-data -- post-data )
37 [ ] [ raw>> ] [ content-type>> ] tri
38 "application/x-www-form-urlencoded" = [ query>assoc ] when
41 : read-post-data ( request -- request )
42 dup method>> "POST" = [
44 [ "content-length" header string>number read ]
45 [ "content-type" header ] tri
46 <post-data> parse-post-data >>post-data
49 : extract-host ( request -- request )
50 [ ] [ url>> ] [ "host" header parse-host ] tri
51 [ >>host ] [ >>port ] bi*
54 : extract-cookies ( request -- request )
55 dup "cookie" header [ parse-cookie >>cookies ] when* ;
57 : read-request ( -- request )
65 GENERIC: write-response ( response -- )
67 GENERIC: write-full-response ( request response -- )
69 : write-response-line ( response -- response )
71 [ "HTTP/" write version>> write bl ]
72 [ code>> present write bl ]
73 [ message>> write crlf ]
76 : unparse-content-type ( request -- content-type )
77 [ content-type>> "application/octet-stream" or ]
78 [ content-charset>> encoding>name ]
80 [ "; charset=" swap 3append ] when* ;
82 : ensure-domain ( cookie -- cookie )
85 host>> dup "localhost" =
89 : write-response-header ( response -- response )
90 #! We send one set-cookie header per cookie, because that's
91 #! what Firefox expects.
92 dup header>> >alist >vector
93 over unparse-content-type "content-type" pick set-at
95 ensure-domain unparse-set-cookie
96 "set-cookie" swap 2array over push
100 : write-response-body ( response -- response )
101 dup body>> call-template ;
103 M: response write-response ( respose -- )
105 write-response-header
109 M: response write-full-response ( request response -- )
111 swap method>> "HEAD" = [
112 [ content-charset>> encode-output ]
113 [ write-response-body ]
117 M: raw-response write-response ( respose -- )
122 M: raw-response write-full-response ( response -- )
125 : post-request? ( -- ? ) request get method>> "POST" = ;
127 SYMBOL: responder-nesting
129 SYMBOL: main-responder
135 ! path is a sequence of path component strings
136 GENERIC: call-responder* ( path responder -- response )
138 TUPLE: trivial-responder response ;
140 C: <trivial-responder> trivial-responder
142 M: trivial-responder call-responder* nip response>> clone ;
144 main-responder global [ <404> <trivial-responder> or ] change-at
146 : invert-slice ( slice -- slice' )
147 dup slice? [ [ seq>> ] [ from>> ] bi head-slice ] [ drop { } ] if ;
149 : add-responder-nesting ( path responder -- )
150 [ invert-slice ] dip 2array responder-nesting get push ;
152 : call-responder ( path responder -- response )
153 [ add-responder-nesting ] [ call-responder* ] 2bi ;
155 : http-error. ( error -- )
156 "Internal server error" [
157 [ print-error nl :c ] with-html-stream
160 : <500> ( error -- response )
161 500 "Internal server error" <trivial-response>
162 swap development? get [ '[ , http-error. ] >>body ] [ drop ] if ;
164 : do-response ( response -- )
165 [ request get swap write-full-response ]
167 [ \ do-response log-error ]
171 [ http-error. ] [ drop "Response error" write ] if
172 ] with-encoded-output
176 LOG: httpd-hit NOTICE
178 LOG: httpd-header NOTICE
180 : log-header ( headers name -- )
181 tuck header 2array httpd-header ;
183 : log-request ( request -- )
184 [ [ method>> ] [ url>> ] bi 2array httpd-hit ]
185 [ { "user-agent" "x-forwarded-for" } [ log-header ] with each ]
188 : split-path ( string -- path )
191 : init-request ( request -- )
193 V{ } clone responder-nesting set ;
195 : dispatch-request ( request -- response )
196 url>> path>> split-path main-responder get call-responder ;
198 : prepare-request ( request -- )
201 [ secure? "https" "http" ? >>protocol ]
202 [ port>> '[ , or ] change-port ]
206 : valid-request? ( request -- ? )
207 url>> port>> local-address get port>> = ;
209 : do-request ( request -- response )
216 [ dup valid-request? [ dispatch-request ] [ drop <400> ] if ]
218 ] [ [ \ do-request log-error ] [ <500> ] bi ] recover ;
220 : ?refresh-all ( -- )
221 development? get-global [ global [ refresh-all ] bind ] when ;
223 LOG: httpd-benchmark DEBUG
225 : ?benchmark ( quot -- )
227 [ benchmark ] [ first ] bi request get url>> rot 3array
229 ] [ call ] if ; inline
231 TUPLE: http-server < threaded-server ;
233 M: http-server handle-client*
236 64 1024 * limit-input
239 [ do-request ] ?benchmark
240 [ do-response ] ?benchmark
243 : <http-server> ( -- server )
244 http-server new-threaded-server
246 "http" protocol-port >>insecure
247 "https" protocol-port >>secure ;
255 : http-insomniac ( -- )
256 "http.server" { "httpd-hit" } schedule-insomniac ;