Как запустить CGI-скрипт из обработчика WSGI?

Я пытаюсь сделать небольшой тестовый сервер для Bugzilla, чтобы я мог тестировать изменения, которые я делаю, прежде чем они будут развернуты на основном сервере на базе Apache. Я лучше всего знаком с Python, и я хотел, чтобы встроенный в Python HTTP-сервер запускал CGI-программы Bugzilla.

К сожалению, Bugzilla предлагает гораздо больше, чем CGI-приложения. У него есть куча CSS и других данных, которые обслуживаются напрямую. Это означает, что обработчик также должен иметь дело с ними. Я хотел бы настроить обработчик WSGI, который просматривает URL-адрес запроса и соответствующим образом направляет запрос либо в один из сценариев Bugzilla CGI, либо извлекает данные непосредственно из файловой системы.

Есть ли лучший способ выполнить то, что я хочу сделать? Если нет, существует ли уже приложение WSGI, которое настроит среду CGI и вызовет приложение CGI через модуль Python subprocess?


person Omnifarious    schedule 05.03.2012    source источник
comment
Вероятно, намного проще просто настроить небольшой файл конфигурации для httpd, который делает то, что вам нужно.   -  person Ignacio Vazquez-Abrams    schedule 05.03.2012
comment
@ IgnacioVazquez-Abrams - я не понимаю, как это может быть проще. Apache всегда казался чрезвычайно болезненным зверем, чтобы заставить его вообще работать. У всех веб-приложений, над которыми я работал, которые были хоть сколько-нибудь приятными, был крошечный веб-сервер, который можно было запустить для целей разработки.   -  person Omnifarious    schedule 06.03.2012
comment
Если проблема заключается в настройке Apache, попробуйте xampp.   -  person Emil M    schedule 06.03.2012
comment
@EmilM: я только что написал свой собственный небольшой веб-сервер Python и создал подкласс CGIHTTPRequestHander. Это было лишь немного сложнее написать, чем файл конфигурации Apache, и я чувствую, что у меня гораздо больше контроля над тем, что именно он делает.   -  person Omnifarious    schedule 06.03.2012


Ответы (2)


Вот решение, которое работает, хотя оно довольно уродливое и довольно медленное. Для каждого сценария 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,)
person Omnifarious    schedule 09.03.2012
comment
На самом деле это не ответ, но это единственное, что решило мою проблему. Если буду писать шлюз сам, то воткну его сюда. - person Omnifarious; 15.03.2012
comment
И я сам написал шлюз. - person Omnifarious; 27.03.2012

Вы используете pybugz? http://www.liquidx.net/pybugz/

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

Кроме того, настроить WSGI через Django и AppEngine очень просто. Довольно быстро устанавливается и, возможно, стоит использовать в качестве обходного пути. Это даст вам тестовый сервер, способный обрабатывать cgi, css и т. д. bugzilla.

Удачи

person Coworker    schedule 09.03.2012
comment
Пока нет, но я бы все равно не хотел переписывать весь пользовательский интерфейс Bugzilla с точки зрения pybugz. И мне нужен общий шлюз WSGI->CGI именно потому, что большое количество различных небольших веб-серверов Python ориентировано на обслуживание WSGI. Мне не нужен конкретный веб-сервер Python, который может работать с WSGI и CGI. Мне нужен шлюз, поэтому у меня есть выбор веб-серверов. - person Omnifarious; 09.03.2012