File: httpfcgi.l

package info (click to toggle)
euslisp 9.27%2Bdfsg-7
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye
  • size: 55,344 kB
  • sloc: ansic: 41,162; lisp: 3,339; makefile: 256; sh: 208; asm: 138; python: 53
file content (183 lines) | stat: -rw-r--r-- 6,111 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
;;;; fastcgi.l
;;;  fast-CGI library
;;;  Copyright (c) 2000, Toshihiro Matsui, Electrotechnical Laboratory
;;;
;;; libfcgi.so is linked and several I/O routines are introduced.
;;; libfcgi.so provides functions to communicate with an apache web server
;;; by a socket channel.   fcgi-devkit-2.1/include/fcgi_stdio.h defines 
;;; stdio macros to redirect references to normal stdin, stdout and stderr
;;; to references to the socket channel.  Note that a simple protocol is
;;; employed in the socket communication to interleave three communication
;;; channels, i.e. stdin, stdout, and stderr in one socket stream.
;;; In order to provide the similar functionality in EusLisp, *standard-input*,
;;; *standard-output*, and *standard-error* streams are redefined with
;;; the fcgi-stream class which uses the libfcgi functions for the low-level
;;; read and write.

(require :httpcgi )

(eval-when (load eval)
 (setq *euslog-file* (format nil "/tmp/eus~d.log" (unix:getpid)))
 (unless (unix:isatty 0)
   (unix:unlink *euslog-file*)
   (setq *euslog* (open *euslog-file* :direction :output))
   (format *euslog* ";; euslisp fcgi started ~s~%"
	(unix:asctime (unix:localtime)))
   )
  )

(defvar *fcgi-stdin*)
(defvar *fcgi-stdout*)
(defvar *fcgi-errout*)

(let ((m (load-foreign "/usr/local/lib/libfcgi.so"))
      (fcgi_sF)   )
   (setq *fcgi-module* m)
   (defforeign fcgi-accept m "FCGI_Accept" () :integer)
   (defforeign fcgi-finish m "FCGI_Finish" () :integer)
   (defforeign fcgi-putchar m "FCGI_putchar" (:integer) :integer)
   (defforeign fcgi-fread m "FCGI_fread"
	 (:string :integer :integer :integer) :integer)
   (defforeign fcgi-fwrite m "FCGI_fwrite"
	 (:string :integer :integer :integer) :integer)
   (setq fcgi_sF (* 4 (send m :find "_fcgi_sF")))
   ;; One fcgi stream is defined as a pair of references to a normal stdio
   ;; and to a fcgx stream.  Therefore, one fcgi stream structure occupies
   ;; 2 pointers, which is 8 bytes on 32-bit word machines.
   (setq *fcgi-stdin* fcgi_sf
	 *fcgi-stdout* (+ fcgi_sf 8)
	 *fcgi-errout* (+ fcgi_sf 16))
   (setq *fcgi-io* (make-foreign-string fcgi_sf 32))
   )


;; fcgi-stream class extends the stream class, defining :flush and :fill
;; with fcgi-fread and fcgi-fwrite.

(defclass fcgi-stream :super stream :slots (port stat))
(defmethod fcgi-stream
 (:init (dir buf p)
   (send-super :init dir buf)
   (setq port p)
   self)
 (:flush ()
    (when (> count 0)
        (setq stat  (fcgi-fwrite buffer 1 count port))
        (setq count 0)) )
 (:fill ()
     (setq tail (fcgi-fread buffer 1 1 port))
     ;; (format *euslog* ":fill tail=~d ch=~d~%" tail (aref buffer  0))
     (setq count 0)  )
 )

(defvar *fcgi-connections*)
(defvar *max-fcgi-connections* 20)

(defclass fcgi-connection :super propertied-object
	:slots (cookie
		http-host
		remote-addr
		remote-port
		access-time
		time-out))

(defmethod fcgi-connection
 (:init (ck &optional (timeout 3600))
    (setf http-host (unix:getenv "HTTP_HOST"))
    (setf remote-addr (unix:getenv "REMOTE_ADDR"))
    (setf remote-port (unix:getenv "REMOTE_PORT"))
    (setq access-time (car (unix:gettimeofday)))
    (setq time-out timeout)
    (setq cookie ck)
    ;; (set-cookie :key "eusid" :value ck)
    self)
 (:set-access-time () (setq access-time (car (unix:gettimeofday))))
 (:cookie () cookie)
 (:find (host ck) 
    (and (equal host http-host) (equal cookie ck)))
 (:delete () 
    (setq *fcgi-connections* (delete self *fcgi-connections*)))
 (:time-out ()
    (if (< (+ time-out access-time) (car (unix:gettimeofday)))
	(send self :delete)))
 )

(defclass fcgi-db-connection :super fcgi-connection
	:slots (db command password))

(defmethod fcgi-db-connection
 (:init (&rest args)
    (setq password nil)
    (send-super* :init args)
    self)
 (:db (&optional newdb)
    (if newdb (setq db newdb))
    db)
 (:delete ()
    (if db (send db :finish))
    (setq db nil)
    (send-super :delete))
 (:password (table &optional (record-id))
    ;; Is the password already authenticated in this connection?
    (member (list table record-id) password :test #'equal))
 (:authenticate-password (pw table &optional (record-id))
    (pushnew (list table record-id) password :test #'equal) )
 )

;; An http session is designated by the value specified by the cookie-key,
;; which can be arbitrarily chosen by an fcgi application.
;; At each http request, fcgi-loop reads a cookie list sent from a client.
;; If there is a value specified by the cooke-key, the *fcgi-connections* list
;; is scanned to find the value in the cookie slot.
;; We assume a particular fcgi application uses a fixed cookie key to
;; represent connections.

(defun fcgi-connection (cookie-key &optional (fcgi-class fcgi-connection))
   ;; scan connections and remove timed out connections
   (dolist (f *fcgi-connections*)
      (send f :time-out))
   (let ((host (unix:getenv "HTTP_HOST"))
	 (cookie (second (assoc cookie-key *cookies*)))
	 (con))
      (setq con (car (member cookie *fcgi-connections*
		 :test #'(lambda (x y) (send y :find host x)))))
      (unless con 
	 (setq con
	    (instance fcgi-class :init (random-cookie) 1200))
	 (pushnew con *fcgi-connections*))
      (send con :set-access-time)
      (setq *fcgi* t)
      con))
	 


(defmacro fcgi-loop (&rest forms)
   `(let ((stat))
	(setq *standard-input*
		(instance fcgi-stream :init :input 64 *fcgi-stdin*))
	(send *standard-input* :reset)
	(setq *standard-output*
		(instance fcgi-stream :init :output 256 *fcgi-stdout*))
	(setq *error-output* 
		(instance fcgi-stream :init :output 256 *fcgi-errout*))
	(setq *terminal-io*
		(instance io-stream :init *standard-input* *standard-output*))
	(setq *cgi-out* *standard-output*)
	;;
	(setq stat (fcgi-accept))
	(while (and (integerp stat) (>= stat  0))
	   (setq *action* (unix:getenv "SCRIPT_NAME"))
	   (with-open-file (pid-file 
			(format nil "/var/run/fcgi/~a.pid"
				(send (pathname (unix:getenv "SCRIPT_NAME")) :name))
			:direction :output)
		(format pid-file "~d~%" (unix:getpid)))
	   (setq *cookies* (get-cookie))
	   ,@forms
	   (fcgi-finish)
	   (setq stat (fcgi-accept))
       t))
   )