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 combinators.short-circuit vocabs.refresh
6 tools.time math present vectors hashtables
21 fry logging logging.insomniac calendar urls
36 GENERIC: write-response ( response -- )
38 GENERIC: write-full-response ( request response -- )
40 : write-response-line ( response -- response )
42 [ "HTTP/" write version>> write bl ]
43 [ code>> present write bl ]
44 [ message>> write crlf ]
47 : unparse-content-type ( request -- content-type )
48 [ content-type>> ] [ content-charset>> ] bi
49 over mime-type-encoding encoding>name or
50 [ "application/octet-stream" or ] dip
51 [ "; charset=" glue ] when* ;
53 : ensure-domain ( cookie -- cookie )
55 url get host>> dup "localhost" =
59 : write-response-header ( response -- response )
60 ! We send one set-cookie header per cookie, because that's
61 ! what Firefox expects.
62 dup header>> >alist >vector
63 over unparse-content-type "content-type" pick set-at
65 ensure-domain unparse-set-cookie
66 "set-cookie" swap 2array suffix!
70 : write-response-body ( response -- response )
71 dup body>> call-template ;
73 M: response write-response ( respose -- )
79 M: response write-full-response ( request response -- )
81 swap method>> "HEAD" = [
82 [ content-encoding>> encode-output ]
83 [ write-response-body ]
87 M: raw-response write-response ( respose -- )
92 M: raw-response write-full-response ( request response -- )
95 : post-request? ( -- ? ) request get method>> "POST" = ;
97 SYMBOL: responder-nesting
99 SYMBOL: main-responder
105 ! path is a sequence of path component strings
106 GENERIC: call-responder* ( path responder -- response )
108 TUPLE: trivial-responder response ;
110 C: <trivial-responder> trivial-responder
112 M: trivial-responder call-responder* nip response>> clone ;
114 main-responder [ <404> <trivial-responder> ] initialize
116 : invert-slice ( slice -- slice' )
117 dup slice? [ [ seq>> ] [ from>> ] bi head-slice ] [ drop { } ] if ;
119 : add-responder-nesting ( path responder -- )
120 [ invert-slice ] dip 2array responder-nesting get push ;
122 : call-responder ( path responder -- response )
123 [ add-responder-nesting ] [ call-responder* ] 2bi ;
125 : make-http-error ( error -- xml )
126 [ "Internal server error" f ] dip
127 [ print-error nl :c ] with-html-writer
130 : <500> ( error -- response )
131 500 "Internal server error" <trivial-response>
132 swap development? get [ make-http-error >>body ] [ drop ] if ;
134 : do-response ( response -- )
135 '[ request get _ write-full-response ]
137 [ \ do-response log-error ]
141 [ make-http-error ] [ drop "Response error" ] if
143 ] with-encoded-output
147 LOG: httpd-hit NOTICE
149 LOG: httpd-header NOTICE
151 : log-header ( request name -- )
152 [ nip ] [ header ] 2bi 2array httpd-header ;
154 : log-request ( request -- )
155 [ [ method>> ] [ url>> ] bi 2array httpd-hit ]
156 [ { "user-agent" "x-forwarded-for" } [ log-header ] with each ]
159 : split-path ( string -- path )
162 : request-params ( request -- assoc )
164 { "GET" [ url>> query>> ] }
165 { "HEAD" [ url>> query>> ] }
166 { "POST" [ post-data>> params>> ] }
171 : param ( name -- value )
174 : set-param ( value name -- )
177 : init-request ( request -- )
180 [ request-params >hashtable params set ] tri
181 V{ } clone responder-nesting set ;
183 : dispatch-request ( request -- response )
184 url>> path>> split-path main-responder get call-responder ;
186 : prepare-request ( request -- )
189 [ secure? "https" "http" ? >>protocol ]
190 [ port>> remap-port '[ _ or ] change-port ]
194 : valid-request? ( request -- ? )
195 url>> port>> remap-port
196 local-address get port>> remap-port = ;
198 : do-request ( request -- response )
205 [ dup valid-request? [ dispatch-request ] [ drop <400> ] if ]
207 ] [ [ \ do-request log-error ] [ <500> ] bi ] recover ;
209 : ?refresh-all ( -- )
210 development? get-global [ [ refresh-all ] with-global ] when ;
212 LOG: httpd-benchmark DEBUG
214 : ?benchmark ( quot -- )
216 [ benchmark ] [ first ] bi url get rot 3array
218 ] [ call ] if ; inline
220 TUPLE: http-server < threaded-server ;
222 SYMBOL: request-limit
224 request-limit [ 64 1024 * ] initialize
226 LOG: httpd-bad-request NOTICE
228 : handle-client-error ( error -- )
230 dup { [ bad-request-line? ] [ parse-error>> got>> empty? ] } 1&&
231 [ drop ] [ httpd-bad-request <400> write-response ] if
234 M: http-server handle-client*
238 request-limit get limited-input
239 [ read-request ] ?benchmark
240 [ do-request ] ?benchmark
241 [ do-response ] ?benchmark
242 ] [ handle-client-error ] recover
245 : <http-server> ( -- server )
246 ascii http-server new-threaded-server
248 "http" protocol-port >>insecure
249 "https" protocol-port >>secure ;
251 : httpd ( port -- http-server )
257 : http-insomniac ( -- )
258 "http.server" { "httpd-hit" } schedule-insomniac ;
260 "http.server.filters" require
261 "http.server.dispatchers" require
262 "http.server.redirection" require
263 "http.server.static" require
264 "http.server.cgi" require