如何从WSGI处理程序启动CGI脚本?

时间:2012-03-05 19:41:13

标签: python cgi wsgi bugzilla

我正在尝试为Bugzilla创建一个小型测试服务器,以便在将它们部署到基于Apache的主服务器之前测试我所做的更改。我最熟悉Python,我想让Python的内置HTTP服务器运行Bugzilla的CGI程序。

不幸的是,Bugzilla拥有的不仅仅是CGI应用程序。它有一堆css和其他直接提供的数据。这意味着处理程序也需要处理这些。我想设置一个WSGI处理程序来查看请求URL,并将请求适当地路由到任何一个Bugzilla CGI脚本或直接从文件系统中提取数据。

有没有更好的方法来完成我想做的事情?如果没有,那么是否有一个WSGI应用程序已经设置了CGI环境并通过Python的subprocess模块调用CGI应用程序?

2 个答案:

答案 0 :(得分:5)

这是一个有效的解决方案,虽然它相当丑陋且有点慢。它需要为您要运行的每个CGI脚本单独的CGIApplication对象。因此,如果您有一个完整的目录,则需要为每个目录实例化一个不同的CGIApplication对象。当你实例化它时,当然取决于你。您可以选择为每个请求实例化新的请求,但如果您以某种方式避免这种情况,您可能会节省一些时间。

import os
import os.path as _osp
import re
import subprocess
import io
import email.parser

env_forward = re.compile('^[A-Z][A-Z0-9_]*$')
header_match = re.compile(b'^(.*?\\n[ \\t\\r]*\\n)(.*)$', re.M | re.S)
env_whitelist = frozenset(('AUTH_TYPE', 'CONTENT_LENGTH', 'CONTENT_TYPE',
                           'DOCUMENT_ROOT', 'QUERY_STRING', 'PATH_INFO',
                           'PATH_TRANSLATED', 'REMOTE_ADDR', 'REMOTE_PORT',
                           'REMOTE_IDENT', 'REMOTE_USER', 'REQUEST_METHOD',
                           'REQUEST_URI', 'SCRIPT_NAME',
                           'SERVER_ADDR', 'SERVER_ADMIN', 'SERVER_NAME',
                           'SERVER_PORT', 'SERVER_PROTOCOL',
                           'SERVER_SIGNATURE', 'SERVER_SOFTWARE'))

class CGIApplication(object):
    def __init__(self, appfname):
        self._appfname = _osp.abspath(appfname)

    def __call__(self, environ, start_respose):
        appenv = {item[0]: item[1] \
                      for item in environ.items() \
                      if ((item[0] in env_whitelist) or
                          item[0].startswith('HTTP_'))}
        appenv['GATEWAY_INTERFACE'] = 'CGI/1.1'
        appenv['PATH'] = '/usr/local/bin:/usr/bin:/bin'
        appenv['SCRIPT_FILENAME'] = self._appfname
        nbytes_for_cgi = appenv.get('CONTENT_LENGTH', '')
        nbytes_for_cgi = (int(nbytes_for_cgi) if nbytes_for_cgi != '' else 0)

        args = [self._appfname]
        query = environ.get('QUERY_STRING', None)
        query = query.replace('+', ' ')
        if '=' not in query:
            args.append(query)
        proc = subprocess.Popen(args,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                env = appenv,
                                cwd = _osp.dirname(self._appfname))
        bytes_read = 0
        data_for_cgi = io.BytesIO()
        while bytes_read < nbytes_for_cgi:
            data = environ['wsgi.input'].read(nbytes_for_cgi - bytes_read)
            bytes_read += len(data)
            data_for_cgi.write(data)
            data = None
        data_for_cgi = data_for_cgi.getvalue()
        output, errdata = proc.communicate(data_for_cgi)
        data_for_cgi = None
        proc.stdin.close()
        proc.stdout.close()
        proc.stderr.close()
        try:
            errdata = errdata.decode('utf-8')
        except UnicodeDecodeError:
            errdata = errdata.decode('iso8859-1')
        environ['wsgi.errors'].write(errdata)
        errdata = None
        if proc.returncode != 0:
            start_respose('500 Internal Server Error',
                          [('Content-Type', 'text/plain')])
            return (b"CGI application died with non-zero return code.\n",)
        else:
            output_hdr = header_match.match(output)
            output_hdr, output = output_hdr.groups()
            parser = email.parser.HeaderParser()
            headers = parser.parsestr(output_hdr.decode('iso8859-1'))
            status = headers.get_all('Status', ['200 OK'])[-1]
            del headers['Status']
            start_respose(status, list(headers.items()))
            return (output,)

答案 1 :(得分:1)

你正在使用pybugz吗? http://www.liquidx.net/pybugz/

http://code.google.com/p/pybugz/

此外,通过Django和AppEngine设置WSGI非常简单。安装起来非常快,可能值得用作解决方法。那会让你成为一个应该能够处理bugzilla的cgi,css等的测试服务器。

祝你好运