-
Notifications
You must be signed in to change notification settings - Fork 1
/
fastcgi.rb
546 lines (450 loc) · 10.7 KB
/
fastcgi.rb
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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
=begin
fastcgi.rb - A FastCGI library
fastcgi.rb Copyright (C) 2001 Eli Green
fcgi.rb Copyright (C) 2002-2003 MoonWolf <[email protected]>
fcgi.rb Copyright (C) 2004 Minero Aoki
fcgi.rb Copyright (C) 2011 saks and Julik Tarkhanov
fcgi.rb Copyright (C) 2012-2013 mva
=end
require 'socket'
require 'stringio'
ProtocolVersion = 1
# Record types
FCGI_BEGIN_REQUEST = 1
FCGI_ABORT_REQUEST = 2
FCGI_END_REQUEST = 3
FCGI_PARAMS = 4
FCGI_STDIN = 5
FCGI_STDOUT = 6
FCGI_STDERR = 7
FCGI_DATA = 8
FCGI_GET_VALUES = 9
FCGI_GET_VALUES_RESULT = 10
FCGI_UNKNOWN_TYPE = 11
FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
FCGI_NULL_REQUEST_ID = 0
# FCGI_BEGIN_REQUSET.role
FCGI_RESPONDER = 1
FCGI_AUTHORIZER = 2
FCGI_FILTER = 3
# FCGI_BEGIN_REQUEST.flags
FCGI_KEEP_CONN = 1
# FCGI_END_REQUEST.protocolStatus
FCGI_REQUEST_COMPLETE = 0
FCGI_CANT_MPX_CONN = 1
FCGI_OVERLOADED = 2
FCGI_UNKNOWN_ROLE = 3
class Server
def initialize(server)
@server = server
@buffers = {}
@default_parameters = {
"FCGI_MAX_CONNS" => 1,
"FCGI_MAX_REQS" => 1,
"FCGI_MPX_CONNS" => true
}
end
def each_request(&block)
graceful = false
trap("SIGUSR1") { graceful = true }
while true
begin
session(&block)
rescue Errno::EPIPE, EOFError
# HTTP request is canceled by the remote user
end
exit 0 if graceful
end
end
def session
sock, addr = *@server.accept
return unless sock
fsock = FastCGISocket.new(sock)
req = next_request(fsock)
yield req
respond_to req, fsock, FCGI_REQUEST_COMPLETE
ensure
sock.close if sock and not sock.closed?
end
private
def next_request(sock)
while rec = sock.read_record
if rec.management_record?
case rec.type
when FCGI_GET_VALUES
sock.send_record handle_GET_VALUES(rec)
else
sock.send_record UnknownTypeRecord.new(rec.request_id, rec.type)
end
else
case rec.type
when FCGI_BEGIN_REQUEST
@buffers[rec.request_id] = RecordBuffer.new(rec)
when FCGI_ABORT_REQUEST
raise "got ABORT_REQUEST" # FIXME
else
buf = @buffers[rec.request_id] or next # inactive request
buf.push rec
if buf.ready?
@buffers.delete rec.request_id
return buf.new_request
end
end
end
end
raise "must not happen: FCGI socket unexpected EOF"
end
def handle_GET_VALUES(rec)
h = {}
rec.values.each_key do |name|
h[name] = @default_parameters[name]
end
ValuesRecord.new(FCGI_GET_VALUES_RESULT, rec.request_id, h)
end
def respond_to(req, sock, status)
split_data(FCGI_STDOUT, req.id, req.out) do |rec|
sock.send_record rec
end
split_data(FCGI_STDERR, req.id, req.err) do |rec|
sock.send_record rec
end if req.err.length > 0
sock.send_record EndRequestRecord.new(req.id, 0, status)
end
DATA_UNIT = 16384
def split_data(type, id, f)
unless f.length == 0
f.rewind
while s = f.read(DATA_UNIT)
yield GenericDataRecord.new(type, id, s)
end
end
yield GenericDataRecord.new(type, id, '')
end
end
class FastCGISocket
def initialize(sock)
@socket = sock
end
def read_record
header = @socket.read(Record::HEADER_LENGTH) or return nil
return nil unless header.size == Record::HEADER_LENGTH
version, type, reqid, clen, padlen, reserved = *Record.parse_header(header)
Record.class_for(type).parse(reqid, read_record_body(clen, padlen))
end
def read_record_body(clen, padlen)
buf = ''
while buf.length < clen
buf << @socket.read([1024, clen - buf.length].min)
end
@socket.read padlen if padlen
buf
end
private :read_record_body
def send_record(rec)
@socket.write rec.serialize
@socket.flush
end
def close
@socket.close
end
end
class RecordBuffer
def initialize(rec)
@begin_request = rec
@envs = []
@stdins = []
@datas = []
end
def push(rec)
case rec
when ParamsRecord
@envs.push rec
when StdinDataRecord
@stdins.push rec
when DataRecord
@datas.push rec
else
raise "got unknown record: #{rec.class}"
end
end
def ready?
case @begin_request.role
when FCGI_RESPONDER
completed?(@envs) and
completed?(@stdins)
when FCGI_AUTHORIZER
completed?(@envs)
when FCGI_FILTER
completed?(@envs) and
completed?(@stdins) and
completed?(@datas)
else
raise "unknown role: #{@begin_request.role}"
end
end
def completed?(records)
records.last and records.last.empty?
end
private :completed?
def new_request
Request.new(@begin_request.request_id, env(), stdin(), nil, nil, data())
end
def env
h = {}
@envs.each {|rec| h.update rec.values }
h
end
def stdin
StringIO.new(@stdins.inject('') {|buf, rec| buf << rec.flagment })
end
def data
StringIO.new(@datas.inject('') {|buf, rec| buf << rec.flagment })
end
end
class Request
def initialize(id, env, stdin, stdout = nil, stderr = nil, data = nil)
@id = id
@env = env
@in = stdin
@out = stdout || StringIO.new
@err = stderr || StringIO.new
@data = data || StringIO.new
end
attr_reader :id
attr_reader :env
attr_reader :in
attr_reader :out
attr_reader :err
attr_reader :data
def finish # for backword compatibility
end
end
class Record
# uint8_t protocol_version;
# uint8_t record_type;
# uint16_t request_id; (big endian)
# uint16_t content_length; (big endian)
# uint8_t padding_length;
# uint8_t reserved;
HEADER_FORMAT = 'CCnnCC'
HEADER_LENGTH = 8
def self.parse_header(buf)
return *buf.unpack(HEADER_FORMAT)
end
def self.class_for(type)
RECORD_CLASS[type]
end
def initialize(type, reqid)
@type = type
@request_id = reqid
end
def version
::FCGI::ProtocolVersion
end
attr_reader :type
attr_reader :request_id
def management_record?
@request_id == FCGI_NULL_REQUEST_ID
end
def serialize
body = make_body()
padlen = body.length % 8
header = make_header(body.length, padlen)
header + body + "\000" * padlen
end
private
def make_header(clen, padlen)
[ProtocolVersion, @type, @request_id, clen, padlen, 0].pack(HEADER_FORMAT)
end
end
class BeginRequestRecord < Record
# uint16_t role; (big endian)
# uint8_t flags;
# uint8_t reserved[5];
BODY_FORMAT = 'nCC5'
def BeginRequestRecord.parse(id, body)
role, flags, *reserved = *body.unpack(BODY_FORMAT)
new(id, role, flags)
end
def initialize(id, role, flags)
super FCGI_BEGIN_REQUEST, id
@role = role
@flags = flags
end
attr_reader :role
attr_reader :flags
def make_body
[@role, @flags, 0, 0, 0, 0, 0].pack(BODY_FORMAT)
end
end
class AbortRequestRecord < Record
def AbortRequestRecord.parse(id, body)
new(id)
end
def initialize(id)
super FCGI_ABORT_REQUEST, id
end
end
class EndRequestRecord < Record
# uint32_t appStatus; (big endian)
# uint8_t protocolStatus;
# uint8_t reserved[3];
BODY_FORMAT = 'NCC3'
def self.parse(id, body)
appstatus, protostatus, *reserved = *body.unpack(BODY_FORMAT)
new(id, appstatus, protostatus)
end
def initialize(id, appstatus, protostatus)
super FCGI_END_REQUEST, id
@application_status = appstatus
@protocol_status = protostatus
end
attr_reader :application_status
attr_reader :protocol_status
private
def make_body
[@application_status, @protocol_status, 0, 0, 0].pack(BODY_FORMAT)
end
end
class UnknownTypeRecord < Record
# uint8_t type;
# uint8_t reserved[7];
BODY_FORMAT = 'CC7'
def self.parse(id, body)
type, *reserved = *body.unpack(BODY_FORMAT)
new(id, type)
end
def initialize(id, t)
super FCGI_UNKNOWN_TYPE, id
@unknown_type = t
end
attr_reader :unknown_type
private
def make_body
[@unknown_type, 0, 0, 0, 0, 0, 0, 0].pack(BODY_FORMAT)
end
end
class ValuesRecord < Record
def self.parse(id, body)
new(id, parse_values(body))
end
def self.parse_values(buf)
result = {}
until buf.empty?
name, value = *read_pair(buf)
result[name] = value
end
result
end
def self.read_pair(buf)
nlen = read_length(buf)
vlen = read_length(buf)
[buf.slice!(0, nlen), buf.slice!(0, vlen)]
end
if "".respond_to?(:bytes) # Ruby 1.9 string semantics
def self.read_length(buf)
if buf[0].bytes.first >> 7 == 0
buf.slice!(0,1)[0].bytes.first
else
buf.slice!(0,4).unpack('N')[0] & ((1<<31) - 1)
end
end
else # Ruby 1.8 string
def self.read_length(buf)
if buf[0] >> 7 == 0
buf.slice!(0,1)[0].bytes.first
else
buf.slice!(0,4).unpack('N')[0] & ((1<<31) - 1)
end
end
end
def initialize(type, id, values)
super type, id
@values = values
end
attr_reader :values
private
def make_body
buf = ''
@values.each do |name, value|
buf << serialize_length(name.length)
buf << serialize_length(value.length)
buf << name
buf << value
end
buf
end
def serialize_length(len)
if len < 0x80
then len.chr
else [len | (1<<31)].pack('N')
end
end
end
class GetValuesRecord < ValuesRecord
def initialize(id, values)
super FCGI_GET_VALUES, id, values
end
end
class ParamsRecord < ValuesRecord
def initialize(id, values)
super FCGI_PARAMS, id, values
end
def empty?
@values.empty?
end
end
class GenericDataRecord < Record
def self.parse(id, body)
new(id, body)
end
def initialize(type, id, flagment)
super type, id
@flagment = flagment
end
attr_reader :flagment
def empty?
@flagment.empty?
end
private
def make_body
if @flagment.respond_to? 'force_encoding' then
return @flagment.dup.force_encoding('BINARY')
else
return @flagment
end
end
end
class StdinDataRecord < GenericDataRecord
def initialize(id, flagment)
super FCGI_STDIN, id, flagment
end
end
class StdoutDataRecord < GenericDataRecord
def initialize(id, flagment)
super FCGI_STDOUT, id, flagment
end
end
class StderrDataRecord < GenericDataRecord
def initialize(id, flagment)
super FCGI_STDERR, id, flagment
end
end
class DataRecord < GenericDataRecord
def initialize(id, flagment)
super FCGI_DATA, id, flagment
end
end
class Record # redefine
RECORD_CLASS = {
FCGI_GET_VALUES => GetValuesRecord,
FCGI_BEGIN_REQUEST => BeginRequestRecord,
FCGI_ABORT_REQUEST => AbortRequestRecord,
FCGI_PARAMS => ParamsRecord,
FCGI_STDIN => StdinDataRecord,
FCGI_DATA => DataRecord,
FCGI_STDOUT => StdoutDataRecord,
FCGI_STDERR => StderrDataRecord,
FCGI_END_REQUEST => EndRequestRecord
}
end