按需启动和停止烧瓶

Starting and stopping flask on demand

我正在编写一个应用程序,它可以公开一个用 flask 实现的简单 RPC 接口。但是我希望能够激活和停用该界面。此外,在同一个 python 解释器中应该可以有应用程序 运行ning 的多个实例,每个实例都有自己的 RPC 接口。

该服务只暴露给本地主机,这是一个原型,所以我不担心安全问题。我正在寻找一个小而简单的解决方案。 这里明显的方法似乎是使用烧瓶开发服务器,但是我找不到关闭它的方法。

我已经为我想要公开的功能创建了一个 flask 蓝图,现在我正在尝试编写一个 class 来包装类似于这样的 RPC 接口:

class RPCInterface:
    def __init__(self, creating_app, config):
        self.flask_app = Flask(__name__)
        self.flask_app.config.update(config)
        self.flask_app.my_app = creating_app

        self.flask_app.register_blueprint(my_blueprint)

        self.flask_thread = Thread(target=Flask.run, args=(self.flask_app,),
                                   name='flask_thread', daemon=True)

    def shutdown(self):
        # Seems impossible with the flask server
        raise NotImplemented()

我正在使用当前应用程序的变量 my_app 将此 RPC 接口正在使用的我的应用程序实例传递到请求的上下文中。

它可以从请求内部关闭(如此处所述 http://flask.pocoo.org/snippets/67/),因此一种解决方案是创建关闭端点并向测试客户端发送请求以启动关闭。但是,为此目的需要一个烧瓶端点。这远非干净。

我查看了 flask 和 werkzeug 的源代码,发现重要的部分(https://github.com/pallets/werkzeug/blob/master/werkzeug/serving.py#L688 处的上下文)如下所示:

def inner():
    try:
        fd = int(os.environ['WERKZEUG_SERVER_FD'])
    except (LookupError, ValueError):
        fd = None
    srv = make_server(hostname, port, application, threaded,
                      processes, request_handler,
                      passthrough_errors, ssl_context,
                      fd=fd)
    if fd is None:
        log_startup(srv.socket)
    srv.serve_forever()

make_server returns werkzeugs 服务器 class 的一个实例,它继承自 pythons http.server class。这又是一个 python BaseSocketServer,它公开了一个关闭方法。问题是这里创建的服务器只是一个局部变量,因此无法从任何地方访问。

这就是我运行走入死胡同的地方。所以我的问题是:

回答我自己的问题以防这种情况再次发生在任何人身上。

第一个解决方案涉及从烧瓶切换到 klein。克莱恩基本上是个功能较少的烧瓶,但运行在扭曲的反应堆之上。这种方式集成非常简单。基本上它是这样工作的:

from klein import Klein
from twisted.internet import reactor

app = Klein()

@app.route('/')
def home(request):
    return 'Some website'

endpoint = serverFromString(reactor, endpoint_string)
endpoint.listen(Site(app.resource()))

reactor.run()

现在可以使用所有扭曲的工具来根据需要启动和停止服务器。

我转向更远的第二个解决方案是摆脱 HTTP 作为传输协议。我在 twisted 的 LineReceiver 协议之上切换到 JSONRPC。这样一切都变得更加简单,而且我没有使用任何 HTTP 东西。

这是一个可怕的骇人听闻的 hack,任何人都不应该将其用于任何目的...除了 maybe 如果您正在尝试编写集成测试套件。可能有更好的方法 - 但如果你正在尝试完全按照问题的要求去做,这里是......

import sys
from socketserver import BaseSocketServer

# implementing the shutdown() method above
def shutdown(self):
    for frame in sys._current_frames().values():
        while frame is not None:
            if 'srv' in frame.f_locals and isinstance(frame.f_locals['srv'], BaseSocketServer):
                frame.f_locals['srv'].shutdown()
                break
        else:
            continue
        break
    self.flask_thread.join()