309 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Factor
		
	
	
		
			Executable File
		
	
			
		
		
	
	
			309 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Factor
		
	
	
		
			Executable File
		
	
| ! Copyright (C) 2003, 2009 Slava Pestov.
 | |
| ! See http://factorcode.org/license.txt for BSD license.
 | |
| USING: kernel accessors sequences arrays namespaces splitting
 | |
| vocabs.loader destructors assocs debugger continuations
 | |
| combinators vocabs.refresh tools.time math math.parser present
 | |
| vectors hashtables
 | |
| io
 | |
| io.sockets
 | |
| io.sockets.secure
 | |
| io.encodings
 | |
| io.encodings.iana
 | |
| io.encodings.utf8
 | |
| io.encodings.ascii
 | |
| io.encodings.binary
 | |
| io.streams.limited
 | |
| io.streams.string
 | |
| io.servers.connection
 | |
| io.timeouts
 | |
| io.crlf
 | |
| fry logging logging.insomniac calendar urls urls.encoding
 | |
| unicode.categories
 | |
| http
 | |
| http.parsers
 | |
| http.server.responses
 | |
| http.server.remapping
 | |
| html.templates
 | |
| html.streams
 | |
| html
 | |
| xml.writer ;
 | |
| FROM: mime.multipart => parse-multipart ;
 | |
| IN: http.server
 | |
| 
 | |
| : check-absolute ( url -- url )
 | |
|     dup path>> "/" head? [ "Bad request: URL" throw ] unless ; inline
 | |
| 
 | |
| : read-request-line ( request -- request )
 | |
|     read-crlf parse-request-line first3
 | |
|     [ >>method ] [ >url check-absolute >>url ] [ >>version ] tri* ;
 | |
| 
 | |
| : read-request-header ( request -- request )
 | |
|     read-header >>header ;
 | |
| 
 | |
| ERROR: no-boundary ;
 | |
| 
 | |
| : parse-multipart-form-data ( string -- separator )
 | |
|     ";" split1 nip
 | |
|     "=" split1 nip [ no-boundary ] unless* ;
 | |
| 
 | |
| SYMBOL: upload-limit
 | |
| 
 | |
| : read-multipart-data ( request -- mime-parts )
 | |
|     [ "content-type" header ]
 | |
|     [ "content-length" header string>number ] bi
 | |
|     unlimited-input
 | |
|     upload-limit get stream-throws limit-input
 | |
|     stream-eofs limit-input
 | |
|     binary decode-input
 | |
|     parse-multipart-form-data parse-multipart ;
 | |
| 
 | |
| : read-content ( request -- bytes )
 | |
|     "content-length" header string>number read ;
 | |
| 
 | |
| : parse-content ( request content-type -- post-data )
 | |
|     [ <post-data> swap ] keep {
 | |
|         { "multipart/form-data" [ read-multipart-data >>params ] }
 | |
|         { "application/x-www-form-urlencoded" [ read-content query>assoc >>params ] }
 | |
|         [ drop read-content >>data ]
 | |
|     } case ;
 | |
| 
 | |
| : read-post-data ( request -- request )
 | |
|     dup method>> "POST" = [
 | |
|         dup dup "content-type" header
 | |
|         ";" split1 drop parse-content >>post-data
 | |
|     ] when ;
 | |
| 
 | |
| : extract-host ( request -- request )
 | |
|     [ ] [ url>> ] [ "host" header parse-host ] tri
 | |
|     [ >>host ] [ >>port ] bi*
 | |
|     drop ;
 | |
| 
 | |
| : extract-cookies ( request -- request )
 | |
|     dup "cookie" header [ parse-cookie >>cookies ] when* ;
 | |
| 
 | |
| : read-request ( -- request )
 | |
|     <request>
 | |
|     read-request-line
 | |
|     read-request-header
 | |
|     read-post-data
 | |
|     extract-host
 | |
|     extract-cookies ;
 | |
| 
 | |
| GENERIC: write-response ( response -- )
 | |
| 
 | |
| GENERIC: write-full-response ( request response -- )
 | |
| 
 | |
| : write-response-line ( response -- response )
 | |
|     dup
 | |
|     [ "HTTP/" write version>> write bl ]
 | |
|     [ code>> present write bl ]
 | |
|     [ message>> write crlf ]
 | |
|     tri ;
 | |
| 
 | |
| : unparse-content-type ( request -- content-type )
 | |
|     [ content-type>> "application/octet-stream" or ] [ content-charset>> ] bi
 | |
|     dup binary eq? [ drop ] [ encoding>name "; charset=" glue ] if ;
 | |
| 
 | |
| : ensure-domain ( cookie -- cookie )
 | |
|     [
 | |
|         url get host>> dup "localhost" =
 | |
|         [ drop ] [ or ] if
 | |
|     ] change-domain ;
 | |
| 
 | |
| : write-response-header ( response -- response )
 | |
|     #! We send one set-cookie header per cookie, because that's
 | |
|     #! what Firefox expects.
 | |
|     dup header>> >alist >vector
 | |
|     over unparse-content-type "content-type" pick set-at
 | |
|     over cookies>> [
 | |
|         ensure-domain unparse-set-cookie
 | |
|         "set-cookie" swap 2array over push
 | |
|     ] each
 | |
|     write-header ;
 | |
| 
 | |
| : write-response-body ( response -- response )
 | |
|     dup body>> call-template ;
 | |
| 
 | |
| M: response write-response ( respose -- )
 | |
|     write-response-line
 | |
|     write-response-header
 | |
|     flush
 | |
|     drop ;
 | |
| 
 | |
| M: response write-full-response ( request response -- )
 | |
|     dup write-response
 | |
|     swap method>> "HEAD" = [
 | |
|         [ content-charset>> encode-output ]
 | |
|         [ write-response-body ]
 | |
|         bi
 | |
|     ] unless drop ;
 | |
| 
 | |
| M: raw-response write-response ( respose -- )
 | |
|     write-response-line
 | |
|     write-response-body
 | |
|     drop ;
 | |
| 
 | |
| M: raw-response write-full-response ( request response -- )
 | |
|     nip write-response ;
 | |
| 
 | |
| : post-request? ( -- ? ) request get method>> "POST" = ;
 | |
| 
 | |
| SYMBOL: responder-nesting
 | |
| 
 | |
| SYMBOL: main-responder
 | |
| 
 | |
| SYMBOL: development?
 | |
| 
 | |
| SYMBOL: benchmark?
 | |
| 
 | |
| ! path is a sequence of path component strings
 | |
| GENERIC: call-responder* ( path responder -- response )
 | |
| 
 | |
| TUPLE: trivial-responder response ;
 | |
| 
 | |
| C: <trivial-responder> trivial-responder
 | |
| 
 | |
| M: trivial-responder call-responder* nip response>> clone ;
 | |
| 
 | |
| main-responder [ <404> <trivial-responder> ] initialize
 | |
| 
 | |
| : invert-slice ( slice -- slice' )
 | |
|     dup slice? [ [ seq>> ] [ from>> ] bi head-slice ] [ drop { } ] if ;
 | |
| 
 | |
| : add-responder-nesting ( path responder -- )
 | |
|     [ invert-slice ] dip 2array responder-nesting get push ;
 | |
| 
 | |
| : call-responder ( path responder -- response )
 | |
|     [ add-responder-nesting ] [ call-responder* ] 2bi ;
 | |
| 
 | |
| : make-http-error ( error -- xml )
 | |
|     [ "Internal server error" f ] dip
 | |
|     [ print-error nl :c ] with-html-writer
 | |
|     simple-page ;
 | |
| 
 | |
| : <500> ( error -- response )
 | |
|     500 "Internal server error" <trivial-response>
 | |
|     swap development? get [ make-http-error >>body ] [ drop ] if ;
 | |
| 
 | |
| : do-response ( response -- )
 | |
|     '[ request get _ write-full-response ]
 | |
|     [
 | |
|         [ \ do-response log-error ]
 | |
|         [
 | |
|             utf8 [
 | |
|                 development? get
 | |
|                 [ make-http-error ] [ drop "Response error" ] if
 | |
|                 write-xml
 | |
|             ] with-encoded-output
 | |
|         ] bi
 | |
|     ] recover ;
 | |
| 
 | |
| LOG: httpd-hit NOTICE
 | |
| 
 | |
| LOG: httpd-header NOTICE
 | |
| 
 | |
| : log-header ( request name -- )
 | |
|     [ nip ] [ header ] 2bi 2array httpd-header ;
 | |
| 
 | |
| : log-request ( request -- )
 | |
|     [ [ method>> ] [ url>> ] bi 2array httpd-hit ]
 | |
|     [ { "user-agent" "x-forwarded-for" } [ log-header ] with each ]
 | |
|     bi ;
 | |
| 
 | |
| : split-path ( string -- path )
 | |
|     "/" split harvest ;
 | |
| 
 | |
| : request-params ( request -- assoc )
 | |
|     dup method>> {
 | |
|         { "GET" [ url>> query>> ] }
 | |
|         { "HEAD" [ url>> query>> ] }
 | |
|         { "POST" [ post-data>> params>> ] }
 | |
|     } case ;
 | |
| 
 | |
| SYMBOL: params
 | |
| 
 | |
| : param ( name -- value )
 | |
|     params get at ;
 | |
| 
 | |
| : set-param ( value name -- )
 | |
|     params get set-at ;
 | |
| 
 | |
| : init-request ( request -- )
 | |
|     [ request set ]
 | |
|     [ url>> url set ]
 | |
|     [ request-params >hashtable params set ] tri
 | |
|     V{ } clone responder-nesting set ;
 | |
| 
 | |
| : dispatch-request ( request -- response )
 | |
|     url>> path>> split-path main-responder get call-responder ;
 | |
| 
 | |
| : prepare-request ( request -- )
 | |
|     [
 | |
|         local-address get
 | |
|         [ secure? "https" "http" ? >>protocol ]
 | |
|         [ port>> remap-port '[ _ or ] change-port ]
 | |
|         bi
 | |
|     ] change-url drop ;
 | |
| 
 | |
| : valid-request? ( request -- ? )
 | |
|     url>> port>> remap-port
 | |
|     local-address get port>> remap-port = ;
 | |
| 
 | |
| : do-request ( request -- response )
 | |
|     '[
 | |
|         _
 | |
|         {
 | |
|             [ prepare-request ]
 | |
|             [ init-request ]
 | |
|             [ log-request ]
 | |
|             [ dup valid-request? [ dispatch-request ] [ drop <400> ] if ]
 | |
|         } cleave
 | |
|     ] [ [ \ do-request log-error ] [ <500> ] bi ] recover ;
 | |
| 
 | |
| : ?refresh-all ( -- )
 | |
|     development? get-global [ global [ refresh-all ] bind ] when ;
 | |
| 
 | |
| LOG: httpd-benchmark DEBUG
 | |
| 
 | |
| : ?benchmark ( quot -- )
 | |
|     benchmark? get [
 | |
|         [ benchmark ] [ first ] bi url get rot 3array
 | |
|         httpd-benchmark
 | |
|     ] [ call ] if ; inline
 | |
| 
 | |
| TUPLE: http-server < threaded-server ;
 | |
| 
 | |
| SYMBOL: request-limit
 | |
| 
 | |
| 64 1024 * request-limit set-global
 | |
| 
 | |
| M: http-server handle-client*
 | |
|     drop [
 | |
|         request-limit get stream-throws limit-input
 | |
|         ?refresh-all
 | |
|         [ read-request ] ?benchmark
 | |
|         [ do-request ] ?benchmark
 | |
|         [ do-response ] ?benchmark
 | |
|     ] with-destructors ;
 | |
| 
 | |
| : <http-server> ( -- server )
 | |
|     ascii http-server new-threaded-server
 | |
|         "http.server" >>name
 | |
|         "http" protocol-port >>insecure
 | |
|         "https" protocol-port >>secure ;
 | |
| 
 | |
| : httpd ( port -- )
 | |
|     <http-server>
 | |
|         swap >>insecure
 | |
|         f >>secure
 | |
|     start-server ;
 | |
| 
 | |
| : http-insomniac ( -- )
 | |
|     "http.server" { "httpd-hit" } schedule-insomniac ;
 | |
| 
 | |
| "http.server.filters" require
 | |
| "http.server.dispatchers" require
 | |
| "http.server.redirection" require
 | |
| "http.server.static" require
 | |
| "http.server.cgi" require
 |