Web插件管理

在v3.3.0,恢复并增强了以前旧版本的blueprint,新增了插件安装功能。

您可以在您的Web应用程序中注册blueprint,并设置蓝图前缀,步骤大概如下:

from flask import Flask
from flask_pluginkit import PluginManager, blueprint
app = Flask(__name__)
PluginManager.init_app(app)
app.register_blueprint(blueprint, url_prefix='/pluginmanager')

启动应用,访问/pluginmanager页面,以我们的 example-fulldemo 为例,效果图是:

_images/webmanager-en.png

这个页面支持中英双语,下面是中文的页面效果:

_images/webmanager-cn.png

使用说明

这个蓝图只有一个页面,这个页面包含三个主要功能:

  • 禁用、启用插件

  • 重载应用

    禁用、启用插件都需要重载应用程序才会生效,为了安全,重载应用程序有一些限制。

    只能在使用Gunicorn或uWSGI后台启动的正式环境中有效,而且要求应用程序的配置项中的 ENV 的值是 production ,另外还需要您手动安装psutil模块,例如: pip install psutil

    如果您使用Gunicorn,需要应用程序的配置项中 PLUGINKIT_GUNICORN_ENABLED 的值为True,并且 PLUGINKIT_PROCESSNAME 的值为具体的程序进程名。

    备注

    Gunicorn后台启动一般是master-worker模式,这里要求的 PLUGINKIT_PROCESSNAME 是master进程名,不包含 gunicorn: master [] ,是 [] 里的进程名,没有默认值。

    如果您使用uWSGI,需要应用程序配置项 PLUGINKIT_UWSGI_ENABLED 的值为True, PLUGINKIT_PROCESSNAME 默认是uwsgi,一般可以不用修改。

    上诉情况满足后才能正常重载应用,以下是示例:

    $ pip install psutil
    
    $ cat app.py
    
    from flask_pluginkit import Flask, blueprint, PluginManager
    app = Flask(__name__)
    app.config.update(
        ENV='production',
        PLUGINKIT_GUNICORN_ENABLED=True,
        PLUGINKIT_PROCESSNAME="app:app"
    )
    pm = PluginManager(app)
    app.register_blueprint(blueprint, url_prefix='/pluginmanager')
    
    $ gunicorn -b 127.0.0.1:5000 app:app --daemon
    
    $ ps aux | grep -v grep | grep gunicorn
    
    root   master-pid   ......   gunicorn: master [app:app]
    root   worker-pid   ......   gunicorn: worker [app:app]
    

    小技巧

    Flask v1.0 新增了ENV配置,默认值是production。

  • 安装插件

    前两个操作很简单,第三个功能需要详细说明下。

    安装插件有三种情况:

    • 上传本地插件

      这个操作会上传一个压缩文件到您的服务器的临时目录中,然后解压到web应用程序的插件目录(由 __init__() 控制),重复上传一个压缩包会覆盖已经解压的文件。

    • 下载远程插件

      类似于上面一种情况,只不过不是上传本地的压缩文件了,而是下载远程的压缩文件到您的服务器的临时目录中,后续步骤跟上面一样。

      这里的远程url要求很严格,它必须是一个有效的压缩文件下载地址,且能解析出有效的文件名(即文件后缀是.tar.gz、.tgz或.zip)。值得一提的是,Flask-PluginKit提供了四种方法自动获取远程url的文件名,基本能满足要求(数字表示优先级):

      1. url添加 plugin_filename 查询参数

      2. 直接从url中解析出文件名

      3. 解析返回头 Content-Disposition

      4. 解析返回头 Content-Type

      输入框中有效的url示例:

      for 1, http://xx.com/download?plugin_filename=xx.zip
      for 2, http://xx.xx.com/plugin-v0.0.1.tar.gz
      for 3 and 4, https://codeload.github.com/saintic/flask-pluginkit-demo/zip/master
      
    • 安装模块

      这种情况是新增的,它是依靠pip模块的接口直接安装远程压缩包,可以是pypi的包,也可以是VCS的项目URL,直接安装到python全局环境中,您需要手动通过 plugin_packages 调用。

      输入框参数示例:

      flask-pluginkit-valine
      git+https://github.com/saintic/flask-pluginkit-demo@master
      

      备注

      由于安装模块需要时间,所以这种情况会开启线程处理,安装成功后交给一个消息队列,页面中会每隔5秒查询下消息并显示。

认证

Web管理页面对于管理员来说其实是危险的,如果其他人随意访问,那么可能会对系统造成危害,因此,访问这个蓝图前,运行了一个钩子函数以验证访问者。

验证访问者目前支持四种主要方法和一种辅助方法,前者通过应用程序配置项 PLUGINKIT_AUTH_METHOD 定义,后者通过 PLUGINKIT_AUTH_AID_METHOD 定义。

  • PLUGINKIT_AUTH_METHOD ,支持四种类型的值。

    • BOOL

      这种方法会通过 flask.g 获取配置里 PLUGINKIT_AUTH_BOOLFIELD 字段的值(默认值是signin),为True时,通过验证。

      示例:

      from flask import Flask, g
      from flask_pluginkit import blueprint, PluginManager
      app = Flask(__name__)
      pm = PluginManager(app)
      app.config.update(
          PLUGINKIT_AUTH_METHOD="BOOL",
          PLUGINKIT_AUTH_BOOLFIELD="auth"
      )
      app.register_blueprint(blueprint, url_prefix='/pluginmanager')
      
      @app.before_request
      def br():
          g.auth = True
      
    • BASIC

      HTTP Basic Auth,简单但不安全的方法,这种方法要求有username和password,所以要求应用程序配置项 PLUGINKIT_AUTH_USERS ,是一个dict,key是username,value是password,支持多个key、value;可选配置 PLUGINKIT_AUTH_REALM 以设置提示信息。

      示例:

      from flask_pluginkit import Flask, blueprint, PluginManager
      app = Flask(__name__)
      pm = PluginManager(app)
      app.config.update(
          PLUGINKIT_AUTH_METHOD="BASIC",
          PLUGINKIT_AUTH_USERS=dict(admin="admin", test="test")
      )
      app.register_blueprint(blueprint, url_prefix='/pluginmanager')
      
    • TOKEN

      这个要求浏览器端携带一个认证头,头的字段由应用程序配置项 PLUGINKIT_AUTH_TOKENFIELD 定义,默认值是AccessToken,同时要求配置一个名叫 PLUGINKIT_AUTH_CHECKTOKEN 的函数或类方法,这个函数要接收一个token参数,Flask-PluginKit会执行此函数,结果非0非空时通过验证。

      示例:

      from flask_pluginkit import Flask, blueprint, PluginManager
      app = Flask(__name__)
      pm = PluginManager()
      pm.init_app(app)
      
      def check_token(token):
          if token == 'test':
              return True
      
      app.config.update(
          PLUGINKIT_AUTH_METHOD="TOKEN",
          PLUGINKIT_AUTH_CHECKTOKEN=check_token
      )
      app.register_blueprint(blueprint, url_prefix='/pluginmanager')
      
    • FUNC

      这应该是最简单的了,由应用程序配置项 PLUGINKIT_AUTH_FUNC 定义一个函数或类方法,没有参数,执行结果非0非空时验证通过。

      示例:

      from flask_pluginkit import Flask, blueprint, PluginManager
      app = Flask(__name__)
      PluginManager(app)
      app.config.update(
          PLUGINKIT_AUTH_METHOD="FUNC",
          PLUGINKIT_AUTH_FUNC=lambda :True
      )
      app.register_blueprint(blueprint, url_prefix='/pluginmanager')
      
  • PLUGINKIT_AUTH_AID_METHOD ,支持一种类型的值。

    • IP

      Flask-PluginKit会获取客户端ip,要求应用程序配置 PLUGINKIT_AUTH_IP_WHITELIST 定义ip白名单,配置 PLUGINKIT_AUTH_IP_BLACKLIST 定义ip黑名单,两个数据类型都是list,只有ip在白名单且不在黑名单时,验证通过。

      示例:

      from flask_pluginkit import Flask, blueprint, PluginManager
      app = Flask(__name__)
      PluginManager(app)
      app.config.update(
          PLUGINKIT_AUTH_AID_METHOD="IP",
          PLUGINKIT_AUTH_IP_WHITELIST=["127.0.0.1"]
      )
      app.register_blueprint(blueprint, url_prefix='/pluginmanager')
      

备注

一个应用程序中,主要方法的四种类型只能使用一种或不使用;辅助方法可以同时与主要方法一起使用,也可以单独使用;无论如何,必须有一种验证方法,否则页面提示权限拒绝。