Как передать параметры в 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()
(за исключением, может быть, в уединении вашего собственного дома)