scheme-libraries/retropikzel/fcgi.scm

220 lines
9.3 KiB
Scheme

(define FCGI-BEGIN-REQUEST 1)
(define FCGI-ABORT-REQUEST 2)
(define FCGI-END-REQUEST 7)
(define FCGI-PARAMS 4)
(define FCGI-STDIN 5)
(define FCGI-STDOUT 6)
(define FCGI-STDERR 7)
(define FCGI-DATA 8)
(define FCGI-GET-VALUES 9)
(define FCGI-GET-VALUES-RESULT 10)
(define FCGI-UNKNOWN-TYPE 11)
(define (fcgi-type->symbol type)
(cond ((= type FCGI-BEGIN-REQUEST) 'FCGI-BEGIN-REQUEST)
((= type FCGI-ABORT-REQUEST) 'FCGI-ABORT-REQUEST)
((= type FCGI-END-REQUEST) 'FCGI-END-REQUEST)
((= type FCGI-PARAMS) 'FCGI-PARAMS)
((= type FCGI-STDIN) 'FCGI-STDIN)
((= type FCGI-STDOUT) 'FCGI-STDOUT)
((= type FCGI-STDERR) 'FCGI-STDERR)
((= type FCGI-DATA) 'FCGI-DATA)
((= type FCGI-GET-VALUES) 'FCGI-GET-VALUES)
((= type FCGI-GET-VALUES-RESULT) 'FCGI-GET-VALUES-RESULT)
((= type FCGI-UNKNOWN-TYPE) 'FCGI-UNKNOWN-TYPE)))
(define FCGI-KEEP-CONN 1)
(define FCGI-RESPONDER 1)
(define FCGI-AUTHORIZER 2)
(define FCGI-FILTER 3)
(define (fcgi-role->symbol role)
(cond ((= role FCGI-RESPONDER) 'FCGI-RESPONDER)
((= role FCGI-AUTHORIZER) 'FCGI-AUTHORIZER)
((= role FCGI-FILTER) 'FCGI-FILTER)))
(define (b1+b0 b1 b0)
;; https://web.archive.org/web/20160119141816/http://www.fastcgi.com/drupal/node/6?q=node%2F22
;; FROM SPECIFICATION:
;; When two adjacent structure components are named identically except for
;; the suffixes "B1" and "B0," it means that the two components may be viewed
;; as a single number, computed as B1<<8 + B0
;; BUT IN CODE THEY DO: (B1<<8) + B0
(+ (arithmetic-shift b1 8) b0))
(define (integer->b1-b0 int)
(let ((b1 (bitwise-and (arithmetic-shift int -8) 255))
(b0 (bitwise-and int 255)))
`((b1 . ,b1) (b0 . ,b0))))
(define (parse-request-content type content)
(cond
((symbol=? type 'FCGI-BEGIN-REQUEST)
(let ((role-b1 (bytevector-u8-ref content 0))
(role-b0 (bytevector-u8-ref content 1))
(flags (bytevector-u8-ref content 2)))
`((role . ,(b1+b0 role-b1 role-b0))
(flags . ,flags))))
((symbol=? type 'FCGI-PARAMS)
;; https://web.archive.org/web/20160119141816/http://www.fastcgi.com/drupal/node/6?q=node%2F22
;; 3.4 Name-Value Pairs
(letrec*
((>>7 (lambda (n) (exact (floor (* n (expt 2 -7))))))
(read-length
(lambda (bv start-index)
(let*
((b0 (bytevector-u8-ref bv (+ start-index 0)))
(b0>>7 (>>7 b0))
(b3 (if (= b0>>7 0) 0 b0))
(b2 (if (= b0>>7 0) 0 (bytevector-u8-ref bv (+ start-index 1))))
(b1 (if (= b0>>7 0) 0 (bytevector-u8-ref bv (+ start-index 2))))
;; Notice redefinition of b0
(b0 (if (= b0>>7 0) b0 (bytevector-u8-ref bv (+ start-index 3))))
(bytes-in-length (if (= b0>>7 0) 1 4)))
(cons bytes-in-length
(if (= b0>>7 0)
b0
;; ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0
(+ (arithmetic-shift (bitwise-and b3 127) 24)
(arithmetic-shift b2 16)
(arithmetic-shift b1 8)
b0))))))
(content-length (bytevector-length content))
(looper
(lambda (start-index result)
(if (>= start-index content-length)
result
(let*
((name-length (read-length content start-index))
(value-length
(read-length content (+ start-index (car name-length))))
(lengths-length (+ (car name-length) (car value-length)))
(name (string->symbol
(utf8->string
(bytevector-copy content
(+ start-index lengths-length)
(+ start-index
lengths-length
(cdr name-length))))))
(value (utf8->string
(bytevector-copy content
(+ start-index
lengths-length
(cdr name-length))
(+ start-index
lengths-length
(cdr name-length)
(cdr value-length))))))
(looper (+ start-index
lengths-length
(cdr name-length)
(cdr value-length))
(append result (list (cons name value)))))))))
(if (= content-length 0)
(bytevector)
(looper 0 '()))))
((symbol=? type 'FCGI-STDIN)
(utf8->string content))
(else content)))
(define (read-request socket)
(let* ((fields (socket-recv socket 8))
(version (bytevector-u8-ref fields 0))
(type (bytevector-u8-ref fields 1))
(type-symbol (fcgi-type->symbol type))
(request-id-b1 (bytevector-u8-ref fields 2))
(request-id-b0 (bytevector-u8-ref fields 3))
(request-id (b1+b0 request-id-b1 request-id-b0))
(content-length-b1 (bytevector-u8-ref fields 4))
(content-length-b0 (bytevector-u8-ref fields 5))
(content-length (b1+b0 content-length-b1 content-length-b0))
(padding-length (bytevector-u8-ref fields 6))
(reserved (bytevector-u8-ref fields 7))
(content-data (if (> content-length 0)
(socket-recv socket content-length)
(bytevector)))
(padding-data (if (> padding-length 0)
(socket-recv socket padding-length)
(bytevector))))
(when (not (= version 1)) (error "Unsupported fastcgi version" version))
`(,type-symbol
(id . ,request-id)
(content . ,(parse-request-content type-symbol content-data)))))
(define (read-requests socket result)
(let ((request (read-request socket)))
(if (symbol=? (car request) 'FCGI-STDIN)
(reverse (cons request result))
(read-requests socket (cons request result)))))
(define (write-response socket type id response-bytes)
(let* ((version 1)
(header-bytes
(let* ((bytes (make-bytevector 8 0))
(id-b1-b0 (integer->b1-b0 id))
(content-length (bytevector-length response-bytes))
(content-length-b1-b0 (integer->b1-b0 content-length))
(padding-length 0)
(reserved 0))
(bytevector-u8-set! bytes 0 version)
(bytevector-u8-set! bytes 1 type)
(bytevector-u8-set! bytes 2 (cdr (assoc 'b1 id-b1-b0)))
(bytevector-u8-set! bytes 3 (cdr (assoc 'b0 id-b1-b0)))
(bytevector-u8-set! bytes 4 (cdr (assoc 'b1 content-length-b1-b0)))
(bytevector-u8-set! bytes 5 (cdr (assoc 'b0 content-length-b1-b0)))
(bytevector-u8-set! bytes 6 padding-length)
(bytevector-u8-set! bytes 7 reserved)
bytes))
(response (bytevector-append header-bytes response-bytes)))
(socket-send socket response)))
(define (check-role role)
(if (= role FCGI-RESPONDER)
role
(error "Unsupported fastcgi role" (fcgi-role->symbol role))))
(define fcgi-internal-handle
(lambda (client-socket thunk)
(let*
((requests (read-requests client-socket '()))
(begin-request (cdr (assoc 'FCGI-BEGIN-REQUEST requests)))
(begin-request-content (cdr (assoc 'content begin-request)))
(id (cdr (assoc 'id begin-request)))
(role (check-role (cdr (assoc 'role begin-request-content))))
(flags (cdr (assoc 'flags begin-request-content)))
(params-request (cdr (assoc 'FCGI-PARAMS requests)))
(headers (cdr (assoc 'content params-request)))
(content-length (string->number (cdr (assoc 'CONTENT_LENGTH headers))))
(query-string (cdr (assoc 'QUERY_STRING headers)))
(parameters '()) ;; TODO
(stdin-request (cdr (assoc 'FCGI-STDIN requests)))
(body (cdr (assoc 'content stdin-request)))
(files '()) ;; TODO
(request '())
(response (parameterize
((current-output-port (open-output-string)))
(apply thunk
(list request
headers
parameters
'() ;; TODO Cookies
body
files))
(get-output-string (current-output-port)))))
(write-response client-socket FCGI-STDOUT id (string->utf8 response))
(write-response client-socket FCGI-STDERR id (make-bytevector 0))
(write-response client-socket FCGI-END-REQUEST id (make-bytevector 0))
(socket-close client-socket))))
(define fcgi-listen
(lambda (socket thunk)
(fcgi-internal-handle (socket-accept socket) thunk)
(fcgi-listen socket thunk)))
(define (handle-request options thunk)
(let ((port (assoc 'port options)))
(when (not port)
(error "handle-request (fcgi) requires port to be passed in options, example: '((port . \"3000\"))"))
(fcgi-listen (make-server-socket (cdr port) *af-inet* *sock-stream* *ipproto-ip*) thunk)))