Как передать параметры в RequestHandler?

Документация Python включает пример создания HTTP-сервера:

def run(server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):
    server_address = ('', 8000)
    httpd = server_class(server_address, handler_class)
    httpd.serve_forever()

A RequestHandler класс предоставляется Server, который затем автоматически обрабатывает обработчик.

Скажем, я хочу передать пользовательские параметры обработчику запроса при его создании. Как я могу это сделать?

В частности, я хочу передать параметры из командной строки, и доступ к sys.argv внутри класса обработчика запроса кажется излишне неуклюжим.

Похоже, что это должно быть возможно, переопределяя части класса Server, но я чувствую, что я пропускаю более простое и лучшее решение.

Ответы

Ответ 1

Используйте класс factory:

def MakeHandlerClassFromArgv(init_args):
    class CustomHandler(BaseHTTPRequestHandler):
        def __init__(self, *args, **kwargs):
             super(CustomHandler, self).__init__(*args, **kwargs)
             do_stuff_with(self, init_args)
    return CustomHandler

if __name__ == "__main__":
    server_address = ('', 8000)
    HandlerClass = MakeHandlerClassFromArgv(sys.argv)
    httpd = HTTPServer(server_address, HandlerClass)
    httpd.serve_forever()

Ответ 2

Я решил это в своем коде, используя "частичное приложение".

Пример написан с использованием Python 3, но частичное приложение работает так же в Python 2:

from functools import partial
from http.server import HTTPServer, BaseHTTPRequestHandler

class ExampleHandler(BaseHTTPRequestHandler):
    def __init__(self, foo, bar, qux, *args, **kwargs):
        self.foo = foo
        self.bar = bar
        self.qux = qux
        # BaseHTTPRequestHandler calls do_GET **inside** __init__ !!!
        # So we have to call super().__init__ after setting attributes.
        super().__init__(*args, **kwargs)

    def do_HEAD(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()

    def do_GET(self):
        self.do_HEAD()
        self.wfile.write('{!r} {!r} {!r}\n'
                         .format(self.foo, self.bar, self.qux)
                         .encode('utf8'))

# We "partially apply" the first three arguments to the ExampleHandler
handler = partial(ExampleHandler, sys.argv[1], sys.argv[2], sys.argv[3])
# .. then pass it to HTTPHandler as normal:
server = HTTPServer(('', 8000), handler)
server.serve_forever()

Это очень похоже на фабрику классов, но, на мой взгляд, у нее есть несколько тонких преимуществ:

  • partial объекты гораздо проще исследовать, что внутри них, чем вложенные классы, определенные и возвращенные фабричными функциями.
  • partial объекты могут быть сериализованы с помощью pickle в современном Python, тогда как определения вложенных классов внутри фабричных функций не могут.
  • В моем ограниченном опыте явное "предварительное присоединение" аргументов с partial к иначе определению Pythonic и нормального класса легче (меньше когнитивная нагрузка) читать, понимать и проверять на правильность, чем определение вложенного класса с параметрами функции-обертки похоронен где-то внутри него.

Единственным реальным недостатком является то, что многие люди не знакомы с partial но, по моему опыту, в любом случае лучше, чтобы все были знакомы с partial, потому что partial способно выскочить как простое и составное решение во многих местах, иногда неожиданно, как здесь.

Ответ 3

Я бы просто прокомментировал ответ Томаса Орозко, но так как я не могу.. Возможно, это поможет другим, которые также столкнутся с этой проблемой. До Python3 у Python есть классы "старого стиля", а BaseHTTPRequestHandler кажется одним из них. Итак, factory должен выглядеть как

def MakeHandlerClassFromArgv(init_args):
    class CustomHandler(BaseHTTPRequestHandler, object):
        def __init__(self, *args, **kwargs):
             do_stuff_with(self, init_args)
             super(CustomHandler, self).__init__(*args, **kwargs)
    return CustomHandler

чтобы избежать ошибок, таких как TypeError: must be type, not classobj.

Ответ 4

Почему бы не просто подклассировать RequestHandler?

class RequestHandler(BaseHTTPRequestHandler):
     a_variable = None

class Server(HTTPServer):
    def serve_forever(self, variable):
        self.RequestHandlerClass.a_variable = variable 
        HTTPServer.serve_forever(self)

def run(server_class=Server, handler_class=RequestHandler):
    server_address = ('', 8000)
    httpd = server_class(server_address, handler_class)
    variable = sys.argv
    httpd.serve_forever(variable)

Ответ 5

Если вам не нужны свойства экземпляра, а только свойства класса, вы можете использовать этот подход:

def run(server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):
    server_address = ('', 8000)
    httpd = server_class(server_address, handler_class)
    httpd.RequestHandlerClass.my_custom_variable = "hello!"
    httpd.serve_forever()

или, может быть, вы могли бы:

def run(server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):
    server_address = ('', 8000)
    httpd = server_class(server_address, handler_class)
    httpd.my_custom_variable = "hello!"
    httpd.serve_forever()

и получить в вашем RequestHandler с:

self.server.my_custom_variable

Ответ 6

Ссылка на подклассы HTTPServer является еще одним вариантом. Переменные на сервере доступны в методах обработчика запросов через self.server.context. Это в основном работает так:

class MyHTTPServer(HTTPServer):

    def __init__(self, *args, **kwargs):
        HTTPServer.__init__(self, *args, **kwargs)
        self.context = SomeContextObject()


class MyHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        context = self.server.context
        ...

# Drawback, Notice that you cannot actually pass the context parameter during constructor creation, but can do it within the __init__ of the MyHTTPServer
server = MyHTTPServer(('', port), MyHandler)
server.serve_forever()

Ответ 7

Никогда не делай этого с global. Используйте фабрику, описанную в других ответах.

CONFIG = None

class MyHandler(BaseHTTPRequestHandler):
     def __init__(self, ...
         self.config = CONFIG       # CONFIG is now 'stuff'

if __name__ == "__main__":
    global CONFIG
    CONFIG = 'stuff'
    server_address = ('', 8000)
    httpd = HTTPServer(server_address, MyHandler)
    httpd.serve_forever()

(за исключением, может быть, в уединении вашего собственного дома)