-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfcgi.tcl
348 lines (248 loc) · 8.6 KB
/
fcgi.tcl
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
package provide fcgi 0.1
namespace eval fcgi {
variable config [dict create]
variable clients [dict create]
variable FCGI_LISTENSOCK_FILENO 0
variable FCGI_HEADER_LEN 8
variable FCGI_VERSION 1
#/** Values for FCGI_Header.type */#
variable FCGI_BEGIN_REQUEST 1
variable FCGI_ABORT_REQUEST 2
variable FCGI_END_REQUEST 3
variable FCGI_PARAMS 4
variable FCGI_STDIN 5
variable FCGI_STDOUT 6
variable FCGI_STDERR 7
variable FCGI_DATA 8
variable FCGI_GET_VALUES 9
variable FCGI_GET_VALUES_RESULT 10
variable FCGI_UNKNOWN_TYPE 11
variable FCGI_MAXTYPE 11
variable FCGI_MINTYPE 1
#/** Value for FCGI_Header.requestId */#
variable FCGI_NULL_REQUEST_ID 0
#/** Mask for FCGI_BeginRequestBody.flags */#
variable FCGI_KEEP_CONN 1
#/** Values for FCGI_BeginRequestBody.role */#
variable FCGI_RESPONDER 1
variable FCGI_AUTHORIZER 2
variable FCGI_FILTER 3
#/** Values for FCGI_EndRequestBody.protocolStatus */#
variable FCGI_REQUEST_COMPLETE 0
variable FCGI_CANT_MPX_CONN 1
variable FCGI_OVERLOADED 2
variable FCGI_UNKNOWN_ROLE 3
#/** Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records */#
variable FCGI_MAX_CONNS "FCGI_MAX_CONNS"
variable FCGI_MAX_REQS "FCGI_MAX_REQS"
variable FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
dict set config $FCGI_MAX_CONNS 1
dict set config $FCGI_MAX_REQS 1
dict set config $FCGI_MPXS_CONNS 0
#---------------------------------------------------------------#
proc config {sock key args} {
variable clients
set argc [llength $args]
if {$argc == 0} {
return [dict get $clients $sock $key]
} elseif {$argc == 1} {
set value [lindex $args 0]
dict set clients $sock $key $value
}
# TODO: check error
}
proc listen {sock} {
variable clients
dict set clients $sock sock $sock
fconfigure $sock -encoding binary -translation binary
}
proc accept {sock idvar} {
variable FCGI_NULL_REQUEST_ID
variable FCGI_UNKNOWN_TYPE
variable FCGI_MAXTYPE
variable FCGI_MINTYPE
variable FCGI_STDIN
while {[recv $sock record]} {
lassign $record version type id contentData contentLength
if {$type > $FCGI_MAXTYPE || $type < $FCGI_MINTYPE} {
send/$FCGI_UNKNOWN_TYPE $sock $type
continue
}
puts "DEBUG: fcgi::recv $sock#$id $type bytes $contentLength"
if {$type==$FCGI_STDIN && $contentLength == 0} {
#XXX: [config $sock response] $sock $id
upvar $idvar ID
set ID $id
return 1
}
recv/$type $sock $id $contentData $contentLength
}
return 0
}
proc destroy {sock} {
variable clients
dict unset clients $sock
}
#---------------------------------------------------------------#
proc send {sock type id {data ""}} {
variable FCGI_VERSION
if {![string is integer $type]} {
set type [set [namespace current]::FCGI_$type]
}
set contentData $data
set contentLength [string length $contentData]
set paddingData ""
set paddingLength [expr {(8 - $contentLength&0x07)&0x07}]
set reserved 0
set packet [binary format "c c Su Su c c a$contentLength a$paddingLength" \
$FCGI_VERSION $type $id $contentLength $paddingLength $reserved \
$contentData $paddingData]
puts -nonewline $sock $packet
flush $sock
}
proc send/$FCGI_UNKNOWN_TYPE {sock type} {
variable FCGI_UNKNOWN_TYPE
variable FCGI_NULL_REQUEST_ID
puts "DEBUG: FCGI_UNKNOWN_TYPE = $type"
set data [binary format {cu a7} $type ""]
send $sock $FCGI_NULL_REQUEST_ID $data
return
}
proc recv {sock recordvar} {
upvar $recordvar record
set record ""
set bytes [read $sock 8]
if {[eof $sock]} {
puts "DEBUG: closed"
destroy $sock
return 0
}
binary scan $bytes {c c Su Su cu c} version type id contentLength paddingLength reserved
set contentData [read $sock $contentLength]
set paddingData [read $sock $paddingLength]
puts "DEBUG: recv $version $type $id $contentLength $paddingLength"
set record [list $version $type $id $contentData $contentLength]
return 1
}
proc data2dict {data} {
puts "DEBUG: data2dict ..."
set skip 0
set size [string length $data]
set result [dict create]
while {$skip < $size} {
#-- binary scan $data "x$skip H64 H64 H64" hex1 hex2 hex3
#-- puts "... hex = $hex1 $hex2 $hex3"
binary scan $data "x$skip cu" nameLength
if {$nameLength & 0x80} {
binary scan $data "x$skip Iu" nameLength
set nameLength [expr {$nameLength & 0x7fffffff}]
incr skip 4
} else {
incr skip 1
}
binary scan $data "x$skip cu" valueLength
if {$valueLength & 0x80} {
binary scan $data "x$skip Iu" valueLength
set valueLength [expr {$valueLength & 0x7fffffff}]
incr skip 4
} else {
incr skip 1
}
binary scan $data "x$skip a$nameLength a$valueLength" name value
dict set result $name $value
#--# puts "DEBUG: ... $skip < $size, $nameLength, $valueLength $name = $value"
incr skip $nameLength
incr skip $valueLength
}
return $result
}
proc recv/$FCGI_BEGIN_REQUEST {sock id data args} {
variable FCGI_RESPONDER
variable FCGI_AUTHORIZER
variable FCGI_FILTER
binary scan $data {Su cu} role flags
if {$role == $FCGI_RESPONDER} {
puts "DEBUG: BEGIN_REQUEST = RESPONDER (flags = $flags)"
} elseif {$role == $FCGI_AUTHORIZER} {
puts "DEBUG: BEGIN_REQUEST = AUTHORIZER (flags = $flags)"
} elseif {$role == $FCGI_FILTER} {
puts "DEBUG: BEGIN_REQUEST = FILTER (flags = $flags)"
} else {
puts "WARN: Unknow Request Role $role"
}
return
}
proc recv/$FCGI_ABORT_REQUEST {sock id data args} {
# TODO: send FCGI_END_REQUEST
puts "DEBUG: abort_request = $role, $flags"
}
proc send/$FCGI_END_REQUEST {sock id} {
variable FCGI_END_REQUEST
variable FCGI_REQUEST_COMPLETE
set appStatus 0
set protocolStatus $FCGI_REQUEST_COMPLETE
puts "DEBUG: end_request = $appStatus, $protocolStatus"
set data [binary format {Iu cu} $appStatus $protocolStatus]
send $sock $FCGI_END_REQUEST $id $data
}
proc recv/$FCGI_PARAMS {sock id data size} {
if {$size==0} {
return
}
dict for {name value} [data2dict $data] {
puts "DEBUG: PARARM $name = $value"
}
}
proc recv/$FCGI_STDIN {sock id data size} {
puts "DEBUG: STDIN = $data"
#-- if {$size == 0} {
#-- [config $sock response] $sock $id $data
#-- }
}
proc recv/$FCGI_DATA {sock id data size} {
puts "DEBUG: DATA = $data"
}
proc send/$FCGI_STDOUT {sock id data} {
variable FCGI_STDOUT
puts "DEBUG: STDOUT = $data"
send $sock $FCGI_STDOUT $id $data
}
proc send/$FCGI_STDERR {sock id data} {
puts "DEBUG: STDERR = $data"
}
proc recv/$FCGI_GET_VALUES {sock id data args} {
variable FCGI_NULL_REQUEST_ID
# assert $id==$FCGI_NULL_REQUEST_ID
variable config
set query [data2dict $data]
set result [dict create]
dict for {name value} $query {
puts "DEBUG: VALUES $name = $value"
if {[dict exists $config $name]} {
dict set result $name [dict get $config $name]
}
}
# dict2data $result
# send ...
# TODO:
}
}
return
FastCGI Record Type
-------------------
* Management Record: ID = 0, also called the null request ID.
* Application Record: ID > 0
* Discrete Record: contains a meaningful unit of data all by itself.
* Stream Record: zero or more non-empty record (length!=0), plus an empty record (length=0).
* FCGI_MAX_CONNS: maximum number of concurrent transport connections, e.g. "1" or "10".
* FCGI_MAX_REQS: maximum number of concurrent requests, e.g. "1" or "50".
* FCGI_MPXS_CONNS: "0" if does not multiplex connections, "1" otherwise.
* The `appStatus` is an application-level status code. Each role documents its usage of appStatus.
* The `protocolStatus` is a protocol-level status code; possible values are:
* FCGI_REQUEST_COMPLETE: normal end of request.
* FCGI_CANT_MPX_CONN: rejecting a new request.
e.g. sends concurrent requests to FCGI_MAX_REQS=1.
* FCGI_OVERLOADED: rejecting a new request.
This happens when the application runs out of some resource, e.g. database connections.
* FCGI_UNKNOWN_ROLE: rejecting a new request.
This happens when the Web server has specified a role that is unknown to the application.