# PCGIPublisher.py
# Persistent CGI Publisher - jeffbauer@bigfoot.com
#
# Copyright (c) 1998, Digital Creations, Fredericksburg, VA, USA. All
# rights reserved. This software includes contributions from Jeff Bauer.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# o Redistributions of source code must retain the above copyright
# notice, this list of conditions, and the disclaimer that follows.
#
# o Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# o All advertising materials mentioning features or use of this
# software must display the following acknowledgement:
#
# This product includes software developed by Digital Creations
# and its contributors.
#
# o Neither the name of Digital Creations nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
#
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS *AS IS* AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# 2.0 alpha4:
# - adds NT support, using INET sockets
# - added Mike Fletcher's Microsoft IIS hack, suggested by Amos Latteier
#
# 2.0 alpha3:
# - adds error checking
# - expands header to 10 bytes (extra byte defines an out-of-band msg)
#
# 2.0 alpha1:
# - add support for new PCGI_ directives
# - re-write module as a class, rather than a set of functions
__version__ = "2.0a4"
import string, sys, os
class PCGIPublisher:
def __init__(self, resource=None):
### resources passed from the environment, info file or pcgi-wrapper
self.errorLogFile = None
self.hostname = None
self.port = 0
self.insertPath = None
self.moduleName = None
self.modulePath = None
self.socketFile = None
self.pidFile = None
self.swhome = None
self.sock = None
### bound imports ###
self.StringIO = None
self.publish_module = None
### other ###
self.bufsize = 8192
self.error = 0
if resource is None:
self.getResources()
else:
self.resource = resource
self.initPCGI()
def cleanup(self):
if self.socketFile:
import os
if os.path.exists(self.socketFile):
try:
os.unlink(self.socketFile)
except os.error:
pass
def getResources(self):
"""
Obtain the publisher resources from the environment. Done here
in a separate method to make it easy to override, because we may
not always and forever obtain our resources from the environment.
"""
import os
self.resource = os.environ
def fatalError(self, errmsg=''):
if self.errorLogFile:
import sys, traceback, StringIO
try:
from time import asctime,localtime,time
timeStamp = asctime(localtime(time()))
except ImportError:
timeStamp = '???'
try:
f = open(self.errorLogFile, 'a+')
f.write("%s %s\n" % (timeStamp, errmsg))
if sys.exc_type != SystemExit:
trace=StringIO.StringIO()
traceback.print_exception(sys.exc_type,
sys.exc_value,
sys.exc_traceback,
None,
trace)
f.write(" %s\n" % trace.getvalue())
f.close()
except IOError:
pass
self.cleanup()
self.error = 1
def initPCGI(self):
import os
self.initPrincipia()
if self.resource.has_key('PCGI_ERROR_LOG'):
self.errorLogFile = self.resource['PCGI_ERROR_LOG']
if self.resource.has_key('PCGI_HOST'):
self.hostname = self.resource['PCGI_HOST']
if self.resource.has_key('PCGI_INSERT_PATH'):
self.insertPath = self.resource['PCGI_INSERT_PATH']
if self.resource.has_key('PCGI_MODULE_PATH'):
self.modulePath = self.resource['PCGI_MODULE_PATH']
if self.resource.has_key('PCGI_NAME'):
self.moduleName = self.resource['PCGI_NAME']
if self.resource.has_key('PCGI_PID_FILE'):
self.pidFile = self.resource['PCGI_PID_FILE']
if self.resource.has_key('PCGI_PORT'):
import string
try: self.port = string.atoi(self.resource['PCGI_PORT'])
except ValueError: pass
if self.resource.has_key('PCGI_SOCKET_FILE'):
self.socketFile = self.resource['PCGI_SOCKET_FILE']
self.insertSysPath()
if not self.moduleName:
return self.fatalError("missing module name, try specifying PCGI_NAME")
### TODO: probably should make an attempt to import self.moduleName
### to provide the user with earliest possible response.
try:
from cStringIO import StringIO
except:
from StringIO import StringIO
self.StringIO = StringIO
try:
from ZPublisher import publish_module
except ImportError:
try:
from cgi_module_publisher import publish_module
except ImportError:
return self.fatalError(
"unable to import publish_module from ZPublisher")
self.publish_module = publish_module
if not self.pidFile:
return self.fatalError("missing pid file")
### create pid file ###
try:
f = open(self.pidFile, 'wb')
f.write(str(os.getpid()))
f.close()
except IOError:
return self.fatalError("unable to write to pid file: %s" % self.pidFile)
import socket
if not self.socketFile:
return self.fatalError("missing socket file")
if self.port:
if os.sep == '/':
return self.fatalError("INET sockets not yet available on Unix")
if self.hostname is None:
self.hostname = socket.gethostname()
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((self.hostname, self.port))
except socket.error:
return self.fatalError("error binding to socket: %s" % self.socketFile)
try:
sf = open(self.socketFile, 'wb')
sf.write("%s\n%s\n" % (self.hostname, self.port))
sf.close()
except IOError:
return self.fatalError("error attempting to write socket file: %s" % self.socketFile)
else:
try: os.unlink(self.socketFile)
except os.error: pass
try:
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.bind(self.socketFile)
except socket.error:
return self.fatalError("error binding to socket: %s" % self.socketFile)
try: os.chmod(self.socketFile, 0777)
except os.error: pass
def initPrincipia(self):
if self.resource.has_key('SOFTWARE_NAME'):
self.moduleName = self.resource['SOFTWARE_NAME']
def insertSysPath(self, insertPath=None):
import os, sys, string
if insertPath is None:
insertPath = self.insertPath
if insertPath:
sys.path[0:0]=string.split(insertPath,':')
elif self.resource.has_key('PCGI_WORKING_DIR'):
### Note: PCGI_WORKING_DIR is a deprecated pcgi directive ###
workDir = self.resource['PCGI_WORKING_DIR']
while workDir[-1:]=='/' or workDir[-1:]=='\\':
workDir=workDir[:-1]
sys.path[0:0]=[workDir]
else:
pass
# The present assumption is that if the module path isn't in
# sys.path, we want it put there, even if the PCGI_INSERT_PATH
# directive has been specified.
if self.modulePath:
d, s = os.path.split(self.modulePath)
if not d in sys.path:
sys.path[0:0] = [d]
# If the moduleName is not known at this time, we make a
# reasonable guess based on the path. It might behoove the
# user to explicitly specify PCGI_NAME, however.
if not self.moduleName:
import string
self.moduleName = string.splitfields(s,'.')[0]
def handler(self, conn):
from string import split,join,atoi
hdr = conn.recv(10)
size = atoi(hdr)
buff = []
while size > 0:
data = conn.recv(size)
size = size - len(data)
buff.append(data)
### XXX - Later: add out-of-band data handling ###
if (hdr[0] != '0'):
return
env = {}
for i in filter(None, split(join(buff,''),'\000')):
e = split(i,'=')
if len(e) >= 2:
env[e[0]] = join(e[1:],'=')
else:
env[e[0]]=''
size = atoi(conn.recv(10))
if size > 1048576:
### write large upload data to a file ###
from tempfile import TemporaryFile
stdin = TemporaryFile('w+b')
bufsize = self.bufsize
while size > 0:
if size < bufsize:
bufsize=size
data = conn.recv(bufsize)
size = size - len(data)
stdin.write(data)
stdin.seek(0,0)
else:
### use StringIO for smaller data ###
buff = []
while size > 0:
data = conn.recv(size)
size = size-len(data)
buff.append(data)
stdin = self.StringIO(join(buff,''))
stdout = self.StringIO()
stderr = self.StringIO()
### IIS hack to fix broken PATH_INFO
### taken from Mike Fletcher's win_cgi_module_publisher
import string
if env.has_key('SERVER_SOFTWARE') and string.find(env['SERVER_SOFTWARE'],'Microsoft-IIS') != -1:
script = filter(None,string.split(string.strip(env['SCRIPT_NAME']),'/'))
path = filter(None,string.split(string.strip(env['PATH_INFO']),'/'))
env['PATH_INFO'] = string.join(path[len(script):],'/')
try:
self.publish_module(self.moduleName,stdin=stdin,stdout=stdout,stderr=stderr,environ=env)
except:
self.fatalError("unable to publish module")
stdin.close()
stdout=stdout.getvalue()
stderr=stderr.getvalue()
stdout_len=len(stdout)
conn.send('%010d' % len(stdout))
to_send=stdout_len
if to_send > 0:
while 1:
sent = conn.send(stdout)
if sent == to_send:
break
else:
to_send = to_send - sent
stdout = stdout[sent:]
stderr_len=len(stderr)
conn.send('%010d' % stderr_len)
to_send=stderr_len
if to_send > 0:
while 1:
sent = conn.send(stderr)
if sent == to_send:
break
else:
to_send = to_send - sent
stderr = stderr[sent:]
conn.close()
def listen(self):
"""
Note to sub-classes: Aside from the constructor, listen() should
be the only method you *must* invoke.
"""
if self.error:
return self.fatalError("attempt to listen after fatal error")
import os, sys
if not self.sock:
return self.fatalError("no socket available")
self.sock.listen(512)
stdlog('stderr')
stdlog('stdout')
while not self.error:
conn, accept = self.sock.accept()
try:
self.handler(conn)
except socket.error:
pass
def stdlog(name):
NAME=string.upper(name)+'_LOG'
if os.environ.has_key(NAME):
f=os.environ[NAME]
else:
f='/dev/null'
try: f=open(f,'a')
except: f=None
getattr(sys, name).close()
if f is not None: setattr(sys, name, f)
def main():
try:
import os, sys, string, traceback
pcgiPublisher = PCGIPublisher(os.environ)
if not pcgiPublisher.error:
pcgiPublisher.listen()
except ImportError:
print "Content-type: text/html"
print
print "PCGIPublisher catastrophic import error"
if __name__ == '__main__':
try:
main()
finally:
import os
if os.environ.has_key('PCGI_SOCKET_FILE'):
try: os.unlink(os.environ['PCGI_SOCKET_FILE'])
except os.error: pass
|