cgi-bin/tek/class/fastcgi.lua
author Timm S. Mueller <tmueller@neoscientists.org>
Mon, 17 Dec 2007 04:47:56 +0100
changeset 227 cf52620e1530
parent 212 a1aad6415384
child 239 08efae95f502
permissions -rw-r--r--
Latest changes merged in, version bumped to 0.4
tmueller@227
     1
-------------------------------------------------------------------------------
tmueller@201
     2
--
tmueller@201
     3
--	tek.class.fastcgi
tmueller@201
     4
--	Written by Timm S. Mueller <tmueller@neoscientists.org>
tmueller@201
     5
--
tmueller@227
     6
--	OVERVIEW::
tmueller@201
     7
--	Lua FastCGI protocol implementation - specifically written for
tmueller@227
     8
--	the ''external server'' configuration (via IP socket).
tmueller@201
     9
--
tmueller@227
    10
--	Methods that must be implemented by the user:
tmueller@227
    11
--		- FastCGI:have_params() - signals that all arguments have been
tmueller@227
    12
--		transferred
tmueller@201
    13
--
tmueller@227
    14
--	Methods that can be implemented by the user:
tmueller@227
    15
--		- FastCGI:update_stream() - stream created/updated (for POST)
tmueller@227
    16
--		- FastCGI:have_stream() - stream completed (for POST)
tmueller@201
    17
--
tmueller@227
    18
--	REQUIREMENTS::
tmueller@227
    19
--	LuaSocket and Bit library
tmueller@227
    20
--
tmueller@227
    21
-------------------------------------------------------------------------------
tmueller@201
    22
tmueller@201
    23
local Class = require "tek.class"
tmueller@201
    24
local socket = require "socket"
tmueller@201
    25
local bit = require "bit"
tmueller@201
    26
tmueller@201
    27
local insert, remove, concat, pairs, ipairs, error, assert =
tmueller@201
    28
	table.insert, table.remove, table.concat, pairs, ipairs, error, assert
tmueller@201
    29
local tonumber, setmetatable, unpack, type =
tmueller@201
    30
	tonumber, setmetatable, unpack, type
tmueller@201
    31
local char = string.char
tmueller@212
    32
local min = math.min
tmueller@201
    33
tmueller@201
    34
module("tek.class.fastcgi", Class)
tmueller@227
    35
_VERSION = "FastCGI 0.4"
tmueller@201
    36
tmueller@201
    37
-------------------------------------------------------------------------------
tmueller@201
    38
-- local FIFO class:
tmueller@201
    39
-------------------------------------------------------------------------------
tmueller@201
    40
tmueller@201
    41
local FIFO = Class:newClass()
tmueller@201
    42
tmueller@201
    43
function FIFO.new(class, self)
tmueller@201
    44
	self = Class.new(class, self or { })
tmueller@201
    45
	self.buf = self.buf or { }
tmueller@201
    46
	return self
tmueller@201
    47
end
tmueller@201
    48
tmueller@201
    49
function FIFO:write(s)
tmueller@201
    50
	if s and s ~= -1 then
tmueller@201
    51
		insert(self.buf, s)
tmueller@201
    52
	elseif self.buf[#self.buf] ~= -1 then
tmueller@201
    53
		insert(self.buf, -1) -- EOF
tmueller@201
    54
	end
tmueller@201
    55
end
tmueller@201
    56
tmueller@201
    57
function FIFO:readn(len)
tmueller@201
    58
	local t, p, l = { }
tmueller@201
    59
	while len > 0 do
tmueller@201
    60
		p = remove(self.buf, 1)
tmueller@201
    61
		if not p then
tmueller@201
    62
			break -- no more data
tmueller@201
    63
		end
tmueller@201
    64
		if p == -1 then
tmueller@201
    65
			insert(self.buf, -1) -- push back EOF
tmueller@201
    66
			break -- end of stream, EOF in next turn
tmueller@201
    67
		end
tmueller@201
    68
		l = p:len()
tmueller@201
    69
		if l > len then -- break buffer fragment in two
tmueller@201
    70
			insert(t, p:sub(1, len))
tmueller@201
    71
			insert(self.buf, 1, p:sub(len + 1))
tmueller@201
    72
			break
tmueller@201
    73
		end
tmueller@201
    74
		insert(t, p)
tmueller@201
    75
		len = len - l
tmueller@201
    76
	end
tmueller@201
    77
	return concat(t)
tmueller@201
    78
end
tmueller@201
    79
tmueller@201
    80
function FIFO:reada()
tmueller@201
    81
	local last = remove(self.buf)
tmueller@201
    82
	if last ~= -1 then -- not EOF
tmueller@201
    83
		insert(self.buf, last) -- push back
tmueller@201
    84
		last = nil
tmueller@201
    85
	end
tmueller@201
    86
	local s = concat(self.buf)
tmueller@201
    87
	self.buf = { last }
tmueller@201
    88
	return s
tmueller@201
    89
end
tmueller@201
    90
tmueller@201
    91
function FIFO:read(...)
tmueller@201
    92
	if self.buf[1] == -1 then
tmueller@201
    93
		return -- EOF
tmueller@201
    94
	end
tmueller@201
    95
	local t, s = { }
tmueller@201
    96
	for _, what in ipairs(arg) do
tmueller@201
    97
		if what == "*a" then
tmueller@201
    98
			s = self:reada()
tmueller@201
    99
		elseif type(what) == "number" then
tmueller@201
   100
			s = self:readn(tonumber(what))
tmueller@201
   101
		else
tmueller@201
   102
			error("unknwon format")
tmueller@201
   103
		end
tmueller@201
   104
		insert(t, s)
tmueller@201
   105
	end
tmueller@201
   106
	return unpack(t)
tmueller@201
   107
end
tmueller@201
   108
tmueller@201
   109
--	FCGI encoded lengths and key/value pairs:
tmueller@201
   110
tmueller@201
   111
function FIFO:readlen()
tmueller@201
   112
	local l = self:read(1)
tmueller@201
   113
	if not l then return end
tmueller@201
   114
	l = l:byte()
tmueller@201
   115
	if l < 128 then
tmueller@201
   116
		return l
tmueller@201
   117
	end
tmueller@201
   118
	local t = self:read(3)
tmueller@201
   119
	if not t then return end
tmueller@201
   120
	return (l % 128) * 16777216 + t:byte(1) * 65536 +
tmueller@201
   121
		t:byte(2) * 256 + t:byte(3)
tmueller@201
   122
end
tmueller@201
   123
tmueller@201
   124
function FIFO:readkeyvals()
tmueller@201
   125
	local t = { }
tmueller@201
   126
	local l1, l2, key, val
tmueller@201
   127
	while true do
tmueller@201
   128
		l1 = self:readlen()
tmueller@201
   129
		if not l1 then break end
tmueller@201
   130
		l2 = self:readlen()
tmueller@201
   131
		if not l2 then break end
tmueller@201
   132
 		key = self:read(l1)
tmueller@201
   133
 		val = self:read(l2)
tmueller@201
   134
		t[key] = val
tmueller@201
   135
	end
tmueller@201
   136
	return t
tmueller@201
   137
end
tmueller@201
   138
tmueller@201
   139
-------------------------------------------------------------------------------
tmueller@201
   140
-- FCGI class:
tmueller@201
   141
-------------------------------------------------------------------------------
tmueller@201
   142
tmueller@201
   143
FCGI_BEGIN_REQUEST = 1
tmueller@201
   144
FCGI_ABORT_REQUEST = 2
tmueller@201
   145
FCGI_END_REQUEST = 3
tmueller@201
   146
FCGI_PARAMS = 4
tmueller@201
   147
FCGI_STDIN = 5
tmueller@201
   148
FCGI_STDOUT = 6
tmueller@201
   149
FCGI_STDERR = 7
tmueller@201
   150
FCGI_DATA = 8
tmueller@201
   151
FCGI_GET_VALUES = 9
tmueller@201
   152
FCGI_GET_VALUES_RESULT = 10
tmueller@201
   153
FCGI_UNKNOWN_TYPE = 11
tmueller@201
   154
tmueller@201
   155
local FCGI_RESPONDER = 1
tmueller@201
   156
local FCGI_REQUEST_COMPLETE = 0
tmueller@201
   157
local FCGI_UNKNOWN_ROLE = 3
tmueller@201
   158
tmueller@201
   159
local function encodelen(buf, l)
tmueller@201
   160
	if l > 127 then
tmueller@201
   161
		insert(buf, char(bit.rshift(bit.band(l, 0x7f000000), 24) + 128))
tmueller@201
   162
		insert(buf, char(bit.rshift(bit.band(l, 0x00ff0000), 16)))
tmueller@201
   163
		insert(buf, char(bit.rshift(bit.band(l, 0x0000ff00), 8)))
tmueller@201
   164
	end
tmueller@201
   165
	insert(buf, char(bit.band(l, 0xff)))
tmueller@201
   166
end
tmueller@201
   167
tmueller@201
   168
local function encodekeyvals(t)
tmueller@201
   169
	local buf = { }
tmueller@201
   170
	for key, val in pairs(t) do
tmueller@201
   171
		encodelen(buf, key:len())
tmueller@201
   172
		encodelen(buf, val:len())
tmueller@201
   173
		insert(buf, key)
tmueller@201
   174
		insert(buf, val)
tmueller@201
   175
	end
tmueller@201
   176
	return concat(buf)
tmueller@201
   177
end
tmueller@201
   178
tmueller@227
   179
local FastCGI = _M
tmueller@201
   180
tmueller@227
   181
function FastCGI.new(class, self)
tmueller@227
   182
	self = Class.new(class, self)
tmueller@201
   183
	self.requests = { }
tmueller@201
   184
	return self
tmueller@201
   185
end
tmueller@201
   186
tmueller@227
   187
function FastCGI:readrecord()
tmueller@201
   188
	local t, err = self.socket:receive(8)
tmueller@201
   189
	if not t then return end
tmueller@201
   190
	if err then error(err) end
tmueller@201
   191
	local r = {
tmueller@201
   192
		ver = t:byte(1),
tmueller@201
   193
		type = t:byte(2),
tmueller@201
   194
		id = t:byte(3) * 256 + t:byte(4),
tmueller@201
   195
		len = t:byte(5) * 256 + t:byte(6)
tmueller@201
   196
	}
tmueller@201
   197
	if r.len == 0 then
tmueller@201
   198
		r.content = ""
tmueller@201
   199
	else
tmueller@201
   200
		r.content, err = self.socket:receive(r.len)
tmueller@201
   201
		if not r.content then return end
tmueller@201
   202
		if err then error(err) end
tmueller@201
   203
	end
tmueller@201
   204
	local pad = t:byte(7)
tmueller@201
   205
	if pad > 0 then
tmueller@201
   206
		t, err = self.socket:receive(pad)
tmueller@201
   207
		if not t then return end
tmueller@201
   208
		if err then error(err) end
tmueller@201
   209
	end
tmueller@201
   210
	return r
tmueller@201
   211
end
tmueller@201
   212
tmueller@227
   213
function FastCGI:write(type, id, s)
tmueller@201
   214
	local totlen = s:len()
tmueller@201
   215
	local totpos = 1
tmueller@201
   216
	while totlen > 0 do
tmueller@212
   217
		local len = min(totlen, 65535)
tmueller@201
   218
		local buf = concat {
tmueller@201
   219
			char(1), -- version
tmueller@201
   220
			char(type), -- type
tmueller@201
   221
			char(bit.rshift(id, 8)), -- id1
tmueller@201
   222
			char(id % 256), -- id0
tmueller@201
   223
			char(bit.rshift(len, 8)), -- len1
tmueller@201
   224
			char(len % 256), -- len0
tmueller@201
   225
			char(0), -- pad = 0
tmueller@201
   226
			char(0), -- reserved
tmueller@201
   227
			s:sub(totpos, totpos + len - 1) -- content
tmueller@201
   228
		}
tmueller@201
   229
		totpos = totpos + len
tmueller@201
   230
		totlen = totlen - len
tmueller@201
   231
tmueller@201
   232
		len = buf:len()
tmueller@201
   233
		local pos, res, err = 1
tmueller@201
   234
		while pos <= len do
tmueller@201
   235
			res, err = self.socket:send(buf, pos)
tmueller@201
   236
			if not res then
tmueller@201
   237
				return nil, err
tmueller@201
   238
			end
tmueller@201
   239
			pos = res + 1
tmueller@201
   240
		end
tmueller@201
   241
	end
tmueller@201
   242
	return true
tmueller@201
   243
end
tmueller@201
   244
tmueller@227
   245
-------------------------------------------------------------------------------
tmueller@227
   246
--	success, msg = write_stdout(req_id, data): Write out data on the FastCGI
tmueller@227
   247
--	stdout stream, in reply to a request of the given Id. The result is true,
tmueller@227
   248
--	indicating success, or nil followed by an error message.
tmueller@227
   249
-------------------------------------------------------------------------------
tmueller@227
   250
tmueller@227
   251
function FastCGI:write_stdout(id, s)
tmueller@201
   252
	return self:write(FCGI_STDOUT, id, s)
tmueller@201
   253
end
tmueller@201
   254
tmueller@227
   255
function FastCGI:collectstream(r)
tmueller@201
   256
	local req = self.requests[r.id]
tmueller@201
   257
	local s = req.streams[r.type]
tmueller@201
   258
	if not s then
tmueller@201
   259
		s = FIFO:new()
tmueller@201
   260
		req.streams[r.type] = s
tmueller@201
   261
	end
tmueller@201
   262
	if r.len == 0 then
tmueller@201
   263
		s:write() -- append EOF
tmueller@201
   264
		return s, true -- finished
tmueller@201
   265
	end
tmueller@201
   266
	s:write(r.content)
tmueller@201
   267
	return s
tmueller@201
   268
end
tmueller@201
   269
tmueller@227
   270
-------------------------------------------------------------------------------
tmueller@227
   271
--	success, msg = endrequest(request[, protstatus[, appstatus]]):
tmueller@227
   272
--	Confirms the end of a FastCGI request. Optionally, the application can
tmueller@227
   273
--	signify a protocol status (by default, FCGI_REQUEST_COMPLETE) and an
tmueller@227
   274
--	application status (by default, 0). See
tmueller@227
   275
--	[[FastCGI protocol][http://www.fastcgi.com/devkit/doc/fcgi-spec.html]]
tmueller@227
   276
--	specification for details.
tmueller@227
   277
--	The result will be true, indicating success, or nil followed by an
tmueller@227
   278
--	error message.
tmueller@227
   279
-------------------------------------------------------------------------------
tmueller@227
   280
tmueller@227
   281
function FastCGI:endrequest(req, protstatus, appstatus)
tmueller@201
   282
	protstatus = protstatus or req.protstatus or FCGI_REQUEST_COMPLETE
tmueller@201
   283
	appstatus = appstatus or req.appstatus or 0
tmueller@201
   284
	self.requests[req.id] = nil -- delete request
tmueller@201
   285
	return self:write(FCGI_END_REQUEST, req.id, concat {
tmueller@201
   286
		char(bit.rshift(bit.band(appstatus, 0x7f000000), 24)),
tmueller@201
   287
		char(bit.rshift(bit.band(appstatus, 0x00ff0000), 16)),
tmueller@201
   288
		char(bit.rshift(bit.band(appstatus, 0x0000ff00), 8)),
tmueller@201
   289
		char(bit.band(appstatus, 0xff)),
tmueller@201
   290
		char(protstatus),
tmueller@201
   291
		char(0), char(0), char(0)
tmueller@201
   292
	})
tmueller@201
   293
end
tmueller@201
   294
tmueller@227
   295
function FastCGI:newrequest(id, role, flags)
tmueller@201
   296
	assert(not self.requests[id])
tmueller@201
   297
	local req = { id = id, role = role, flags = flags, streams = { } }
tmueller@201
   298
	self.requests[id] = req
tmueller@201
   299
	return req
tmueller@201
   300
end
tmueller@201
   301
tmueller@227
   302
function FastCGI:processrecord(r)
tmueller@201
   303
tmueller@201
   304
	local c = r.content
tmueller@201
   305
	local req = self.requests[r.id]
tmueller@201
   306
tmueller@201
   307
	if r.type == FCGI_BEGIN_REQUEST then
tmueller@201
   308
		assert(not req)
tmueller@201
   309
		local role = c:byte(1) * 256 + c:byte(2)
tmueller@201
   310
		if role == FCGI_RESPONDER then
tmueller@201
   311
			-- new request
tmueller@201
   312
			local flags = c:byte(3)
tmueller@201
   313
			req = self:newrequest(r.id, role, flags)
tmueller@201
   314
			return true -- continue
tmueller@201
   315
		end
tmueller@201
   316
		-- unknown role
tmueller@201
   317
		return self:endrequest(req, FCGI_UNKNOWN_ROLE)
tmueller@201
   318
	end
tmueller@201
   319
tmueller@201
   320
	if not req then -- request already closed
tmueller@201
   321
		return true -- continue
tmueller@201
   322
	end
tmueller@201
   323
tmueller@201
   324
	if r.type == FCGI_ABORT_REQUEST then
tmueller@201
   325
		return self:abortrequest(req)
tmueller@201
   326
tmueller@201
   327
	elseif r.type == FCGI_GET_VALUES then
tmueller@201
   328
		local s, fin = self:collectstream(r)
tmueller@201
   329
		if s and fin then
tmueller@201
   330
			local res = { }
tmueller@201
   331
			for k in pairs(s:readkeyvals()) do
tmueller@201
   332
				if k == "FCGI_MAX_CONNS" then
tmueller@201
   333
					res.FCGI_MAX_CONNS = "16"
tmueller@201
   334
				elseif k == "FCGI_MAX_REQS" then
tmueller@201
   335
					res.FCGI_MAX_CONNS = "32"
tmueller@201
   336
				elseif k == "FCGI_MAX_REQS" then
tmueller@201
   337
					res.FCGI_MAX_CONNS = "1"
tmueller@201
   338
				end
tmueller@201
   339
			end
tmueller@201
   340
			res = encodekeyvals(res)
tmueller@201
   341
			return self:write(FCGI_GET_VALUES_RESULT, 0, res)
tmueller@201
   342
		end
tmueller@201
   343
tmueller@201
   344
	elseif r.type == FCGI_PARAMS then
tmueller@201
   345
		local s, fin = self:collectstream(r)
tmueller@201
   346
		if s and fin then
tmueller@201
   347
			req.params = s:readkeyvals()
tmueller@201
   348
			return self:have_params(req, req.params)
tmueller@201
   349
		end
tmueller@201
   350
tmueller@201
   351
	elseif r.type == self.FCGI_STDIN or r.type == self.FCGI_DATA then
tmueller@201
   352
		local s, fin = self:collectstream(r)
tmueller@201
   353
		if fin then
tmueller@201
   354
			if self.have_stream then
tmueller@201
   355
				return self:have_stream(req, r.type, s)
tmueller@201
   356
			end
tmueller@201
   357
		else
tmueller@201
   358
			if self.update_stream then
tmueller@201
   359
				return self:update_stream(req, r.type, s)
tmueller@201
   360
			end
tmueller@201
   361
		end
tmueller@201
   362
tmueller@201
   363
	else
tmueller@201
   364
		-- unknown record
tmueller@201
   365
		local buf = char(r.type) .. char(0):rep(7)
tmueller@201
   366
		return self:write(FCGI_UNKNOWN_TYPE, 0, buf)
tmueller@201
   367
	end
tmueller@201
   368
tmueller@201
   369
	return true -- continue
tmueller@201
   370
end
tmueller@201
   371
tmueller@227
   372
-------------------------------------------------------------------------------
tmueller@227
   373
--	serve(socket): Serve the FastCGI protocol on the supplied socket until
tmueller@227
   374
--	FastCGI:stop() is called.
tmueller@227
   375
-------------------------------------------------------------------------------
tmueller@227
   376
tmueller@227
   377
function FastCGI:serve(socket)
tmueller@201
   378
	assert(socket)
tmueller@201
   379
	self.socket = socket
tmueller@201
   380
	self.serve = true
tmueller@201
   381
	while self.serve do
tmueller@201
   382
		local r = self:readrecord()
tmueller@201
   383
		if not r then
tmueller@201
   384
			break
tmueller@201
   385
		end
tmueller@201
   386
		if not self:processrecord(r) then
tmueller@201
   387
			break
tmueller@201
   388
		end
tmueller@201
   389
	end
tmueller@201
   390
end
tmueller@201
   391
tmueller@227
   392
-------------------------------------------------------------------------------
tmueller@227
   393
--	stop(): Stop acting as a server. This will cause FastCGI:serve() to
tmueller@227
   394
--	return.
tmueller@227
   395
-------------------------------------------------------------------------------
tmueller@227
   396
tmueller@227
   397
function FastCGI:stop()
tmueller@201
   398
	self.serve = false
tmueller@201
   399
end
tmueller@201
   400
tmueller@227
   401
-------------------------------------------------------------------------------
tmueller@227
   402
--	success, msg = abortrequest(req_id): Called when the webserver aborts the
tmueller@227
   403
--	request of the given Id. The return value is the result from
tmueller@227
   404
--	FastCGI:endrequest().
tmueller@227
   405
-------------------------------------------------------------------------------
tmueller@227
   406
tmueller@227
   407
function FastCGI:abortrequest(req)
tmueller@201
   408
	-- request aborted by webserver, confirm:
tmueller@201
   409
	return self:endrequest(req)
tmueller@201
   410
end
tmueller@227
   411
tmueller@227
   412
-------------------------------------------------------------------------------
tmueller@227
   413
--	continue = have_params(req_id, params):
tmueller@227
   414
--	Signals the arrival of all parameters that belong to the request of the
tmueller@227
   415
--	given Id; params is a table containing all received parameters as
tmueller@227
   416
--	key/value pairs. The return value is a boolean indicating success and,
tmueller@227
   417
--	effectively, whether processing of the request should continue.
tmueller@227
   418
--	This method must be implemented by the user before anything useful can be
tmueller@227
   419
--	done with a FastCGI request.
tmueller@227
   420
-------------------------------------------------------------------------------