python 如何将参数传递给RequestHandler?

xuo3flqw  于 2023-05-21  发布在  Python
关注(0)|答案(9)|浏览(191)

Python文档包括an example of creating an HTTP server

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

RequestHandler类提供给Server,然后由Server自动示例化处理程序。
假设我想在创建请求处理程序时将自定义参数传递给它。我怎么能和应该这样做?
更具体地说,我想从命令行传入参数,而必须在请求处理程序类中访问sys.argv似乎不必要地笨拙。
通过覆盖Server类的一部分似乎可以做到这一点,但我觉得我忽略了一个更简单、更好的解决方案。

zbq4xfa0

zbq4xfa01#

我在代码中使用“部分应用”解决了这个问题。
示例使用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对象比工厂函数定义和返回的嵌套类更容易内省它们内部的内容。
  • 在现代Python中,partial对象可以用pickle序列化,而工厂函数中的嵌套类定义则不能(至少在不特意在类上编写__reduce__方法的情况下不能)。
  • 在我有限的经验中,用partial将参数显式地“预附加”到其他Python和正常的类定义中,比嵌套的类定义更容易阅读,理解和验证正确性(认知负荷更少), Package 函数的参数隐藏在其中的某个地方。

唯一真实的的缺点是,许多人不熟悉partial-但根据我的经验,无论如何,* 每个人 * 最好 * 熟悉partial,因为partial在许多地方都有一种方式作为一个简单且可组合的解决方案出现,有时出乎意料,比如这里。

gojuced7

gojuced72#

使用类工厂:

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()
6ljaweal

6ljaweal3#

在写这篇文章的时候,这里所有的答案基本上都坚持(非常尴尬的)意图,即socketserver模块的作者似乎认为传入的处理程序是一个类(即构造器)。实际上,处理程序唯一需要的是它是可调用的,因此我们可以通过使处理程序类的示例可调用并在调用时让它们运行超类的__init__代码来解决socketserver API。在Python 3中:

class MyHandler(http.server.BaseHTTPRequestHandler):
    def __init__(self, message):
        self.message = message

    def __call__(self, *args, **kwargs):
        """Handle a request."""
        super().__init__(*args, **kwargs)

    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(self.message.encode("utf-8"))

这使得超类“constructor”调用不在__init__中,从而消除了在子类的构造函数完成之前分派请求(来自超类的构造函数)的可能性。注意,__init__覆盖必须存在以转移执行,即使初始化不需要它;使用pass的空实现将起作用。
通过这种设计,奇怪的接口被隐藏起来,使用API看起来更自然:

handler = MyHandler("Hello world")
server = http.server.HTTPServer(("localhost", 8000), handler)
server.serve_forever()
lf3rwulv

lf3rwulv4#

我只想对托马斯·奥罗斯科的回答发表评论,但既然我不能…也许这会帮助其他也遇到这个问题的人。在Python3之前,Python有“旧式”类,BaseHTTPRequestHandler似乎就是其中之一。所以,工厂应该看起来像

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这样的错误。

vfh0ocws

vfh0ocws5#

为什么不直接子类化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)
yquaqz18

yquaqz186#

Ref子类化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()
trnvg8h3

trnvg8h37#

如果你不需要示例属性,而只需要类属性,你可以使用这种方法:

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
b4qexyjb

b4qexyjb8#

使用lambda是一种非常简单的方法来创建一个新函数,该函数接受请求处理程序args并创建您的自定义类。
这里我想传递一个将在do_POST()中使用的变量,并设置SimpleHTTPRequestHandler使用的目录,因此setup调用

HTTPServer(('', 8001), lambda *_: _RequestHandler("[1, 2]", *_, directory=sys.path[0]))

完整程序:

from http.server import HTTPServer, SimpleHTTPRequestHandler

import sys

class _RequestHandler(SimpleHTTPRequestHandler):
  def __init__(self, x, *args, **kwargs):
    self.x = x # NEEDS TO HAPPEN BEFORE super().__init__()
    super().__init__(*args, **kwargs)

  def _set_headers(self):
    self.send_response(200)
    self.send_header('Content-type', 'application/json')
    self.end_headers()

  def do_POST(self):
    print("POST")
    length = int(self.headers.get('content-length'))
    message = self.rfile.read(length).decode('utf-8')
    print(message)

    self._set_headers()
    self.wfile.write(self.x.encode('utf-8'))

def run_server():
  server_address = ('', 8001)
  httpd = HTTPServer(server_address, lambda *_: _RequestHandler("[1, 2]", *_, directory=sys.path[0]))
  print('serving http://localhost:8001')
  httpd.serve_forever()

if __name__ == '__main__':
  run_server()
7nbnzgx9

7nbnzgx99#

你可以使用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()

请谨慎使用,例如在您自己家中的隐私处。

相关问题