219 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Factor
		
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Factor
		
	
	
| ! Copyright (C) 2010 Brennan Cheung.
 | |
| ! See http://factorcode.org/license.txt for BSD license.
 | |
| 
 | |
| ! This version of the FastCGI library only supports single connections.
 | |
| ! As opposed to multiplexing multiple requests across a single
 | |
| ! connection.
 | |
| !
 | |
| ! It also expects the following FastCGI parameters to be present:
 | |
| !     * REQUEST_METHOD
 | |
| !     * REQUEST_URI
 | |
| !
 | |
| ! The following are recommended:
 | |
| !     * HTTP_USER_AGENT
 | |
| !     * REMOTE_ADDR
 | |
| 
 | |
| USING: accessors alien.enums alien.syntax assocs combinators
 | |
| combinators.smart formatting http http.server
 | |
| http.server.responses io io.binary io.directories
 | |
| io.encodings.binary io.files io.servers io.sockets
 | |
| io.streams.byte-array kernel locals math namespaces pack
 | |
| prettyprint sequences sequences.deep strings threads
 | |
| urls.encoding ;
 | |
| 
 | |
| IN: fastcgi
 | |
| 
 | |
| SYMBOL: fcgi-server
 | |
| SYMBOL: fcgi-role
 | |
| SYMBOL: fcgi-flags
 | |
| SYMBOL: fcgi-params
 | |
| SYMBOL: fcgi-request
 | |
| SYMBOL: stdin-data
 | |
| 
 | |
| CONSTANT: fcgi-version 1
 | |
| CONSTANT: socket-path "/chroot/web/var/run/factor.sock"
 | |
| 
 | |
| TUPLE: fcgi-header version type request-id content-length
 | |
|     padding-length reserved ;
 | |
| 
 | |
| 
 | |
| ENUM: fcgi-header-types
 | |
|     { FCGI_BEGIN_REQUEST 1 }
 | |
|     FCGI_ABORT_REQUEST
 | |
|     FCGI_END_REQUEST
 | |
|     FCGI_PARAMS
 | |
|     FCGI_STDIN
 | |
|     FCGI_STDOUT
 | |
|     FCGI_STDERR
 | |
|     FCGI_DATA
 | |
|     FCGI_GET_VALUES
 | |
|     FCGI_GET_VALUES_RESULT
 | |
|     FCGI_UNKNOWN_TYPE
 | |
|     { FCGI_MAXTYPE 11 } ;
 | |
| 
 | |
| ENUM: fcgi-roles
 | |
|     { FCGI_RESPONDER 1 }
 | |
|     FCGI_AUTHORIZER
 | |
|     FCGI_FILTER ;
 | |
| 
 | |
| ENUM: fcgi-protocol-status
 | |
|     { FCGI_REQUEST_COMPLETE 0 }
 | |
|     FCGI_CANT_MAX_CONN
 | |
|     FCGI_OVERLOADED
 | |
|     FCGI_UNKNOWN_ROLE ;
 | |
| 
 | |
| :: debug-print ( print-quot -- )
 | |
|     [ print-quot call flush ] with-global ; inline
 | |
| 
 | |
| ! read either a 1 byte or 4 byte big endian integer
 | |
| : read-var-int ( -- n/f )
 | |
|     read1 [
 | |
|         dup 7 bit?
 | |
|         [ 127 bitand 3 read swap suffix be> ] when
 | |
|     ] [ f ] if* ;
 | |
| 
 | |
| :: store-key-value-param ( key value -- )
 | |
|     request tget value key set-header drop ;
 | |
| 
 | |
| : read-params ( -- )
 | |
|     [
 | |
|         read-var-int read-var-int 2dup and
 | |
|         [
 | |
|             [ read >string ] bi@
 | |
|             store-key-value-param
 | |
|             t
 | |
|         ] [ 2drop f ] if
 | |
|     ] loop ;
 | |
|     
 | |
| : delete-if-exists ( file -- )
 | |
|     dup exists? [ delete-file ] [ drop ] if ;
 | |
| 
 | |
| : make-local-socket ( socket-path -- socket )
 | |
|     [ delete-if-exists ] keep
 | |
|     <local> ;
 | |
| 
 | |
| : get-header ( -- header )
 | |
|     "CCSSCC" read-packed-be
 | |
|     [ fcgi-header boa ] input<sequence
 | |
|     dup type>> fcgi-header-types number>enum >>type ;
 | |
| 
 | |
| : get-content-data ( header -- content )
 | |
|     dup
 | |
|     [ content-length>> ]
 | |
|     [ padding-length>> ] bi or 0 > ! because 0 read blocks
 | |
|     [
 | |
|         [ content-length>> read ]
 | |
|         [ padding-length>> read drop ] bi
 | |
|     ] [ drop f ] if ;
 | |
| 
 | |
| : begin-request-body ( seq -- )
 | |
|     binary [ "SCCCCCC" read-packed-be ] with-byte-reader
 | |
|     first2 fcgi-flags tset fcgi-roles
 | |
|     number>enum fcgi-role tset ;
 | |
| 
 | |
| : process-begin-request ( header -- )
 | |
|     get-content-data begin-request-body ;
 | |
| 
 | |
| : process-params ( header -- )
 | |
|     get-content-data binary [ read-params ] with-byte-reader ;
 | |
| 
 | |
| :: make-response-packet ( content -- seq )
 | |
|     [
 | |
|         fcgi-version             ! version
 | |
|         FCGI_STDOUT enum>number  ! type
 | |
|         1                        ! request id
 | |
|         content length           ! content length
 | |
|         0                        ! padding length
 | |
|         0                        ! reserved
 | |
|     ] output>array
 | |
|     "CCSSCC" pack-be content append ;
 | |
| 
 | |
| :: make-end-request-body ( app-status protocol-status -- seq )
 | |
|     [ app-status protocol-status 0 0 0 ] output>array
 | |
|     "ICCCC" pack-be ;
 | |
| 
 | |
| : make-end-request ( -- seq )
 | |
|     [
 | |
|         fcgi-version                   ! version
 | |
|         FCGI_END_REQUEST enum>number   ! type
 | |
|         1                              ! request id
 | |
|         8                              ! content length (always 8 for end-request-body)
 | |
|         0                              ! padding length
 | |
|         0                              ! reserved
 | |
|         0 0 make-end-request-body
 | |
|     ] output>array flatten ;
 | |
| 
 | |
| : write-response ( content -- )
 | |
|     make-response-packet write make-end-request write ;
 | |
| 
 | |
| :: append-stdin-data ( str -- )
 | |
|     stdin-data [ str append ] tchange ;
 | |
| 
 | |
| ! process a header and determine whether we are
 | |
| ! expecting more input
 | |
| : dispatch-by-header ( header -- ? )
 | |
|     dup type>>
 | |
|     {
 | |
|         { FCGI_BEGIN_REQUEST [ process-begin-request t ] }
 | |
|         { FCGI_PARAMS [ process-params t ] }
 | |
|         { FCGI_STDIN [ get-content-data dup append-stdin-data length 0 > ] }  ! keep going until STDIN empty
 | |
|         { FCGI_DATA [ [ "FCGI_DATA ------------------\n" print ] debug-print get-content-data [ >string . ] debug-print f ] }
 | |
|         [ [ "unkown packet type" print ] debug-print drop [ . ] debug-print f ]
 | |
|     } case ;
 | |
| 
 | |
| : make-new-request ( -- )
 | |
|     <request> request tset ;
 | |
| 
 | |
| : parse-packets ( -- )
 | |
|     [ get-header dispatch-by-header ] loop ;
 | |
| 
 | |
| : post? ( -- ? ) request tget method>> "POST" = ;
 | |
| 
 | |
| :: handle-post-data* ( post-data data params -- )
 | |
|     post-data data >>data params >>params
 | |
|     request tget swap >>post-data drop ;
 | |
| 
 | |
| : handle-post-data ( -- )
 | |
|     post? [
 | |
|         request tget dup "CONTENT_TYPE" header
 | |
|         <post-data> [ >>post-data ] keep nip
 | |
|         stdin-data tget >string dup query>assoc
 | |
|         handle-post-data*
 | |
|     ] when ;
 | |
| 
 | |
| : prepare-request ( -- )
 | |
|     request tget
 | |
|     dup "REQUEST_METHOD" header >>method
 | |
|     dup "REQUEST_URI" header >>url
 | |
|     handle-post-data
 | |
|     [ . ] debug-print ;
 | |
| 
 | |
| : fcgi-handler ( -- )
 | |
|     make-new-request parse-packets 
 | |
|     prepare-request
 | |
|     "/path" main-responder get call-responder* 
 | |
|     [ content-type>> "\n\n" append ] [ body>> ] bi append write-response ;
 | |
| 
 | |
| : <fastcgi-server> ( addr -- server )
 | |
|     binary
 | |
|     <threaded-server>
 | |
|       swap >>insecure
 | |
|       "fastcgi-server" >>name
 | |
|       [ fcgi-handler ] >>handler ;
 | |
| 
 | |
| : test-output ( -- str )
 | |
|     "<pre>" 
 | |
|     request tget header>> [ "%s => %s\n" sprintf ] { }
 | |
|     assoc>map concat append
 | |
|     "</pre>" append ;
 | |
| 
 | |
| TUPLE: test-responder ;
 | |
| C: <test-responder> test-responder
 | |
| M: test-responder call-responder* 2drop test-output "text/html" <content> ;
 | |
| 
 | |
| : do-it ( -- )
 | |
|     <test-responder> main-responder set
 | |
|     socket-path [ delete-if-exists ] keep
 | |
|     make-local-socket <fastcgi-server> dup fcgi-server set
 | |
|     start-server drop ;
 |