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 ;
 |